├── .codeclimate.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .size-snapshot.json ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── docs ├── API.md ├── flow.md └── performance.md ├── package.json ├── scripts ├── getPackageNames.js ├── installNestedPackageDeps.js ├── jest.setup.js ├── release.js └── rollup.config.js ├── src ├── basePackage.json └── packages │ ├── recompose-relay │ ├── .npmignore │ ├── README.md │ ├── VERSION │ ├── createContainer.js │ ├── index.js │ ├── package.json │ └── yarn.lock │ ├── recompose │ ├── .npmignore │ ├── README.md │ ├── VERSION │ ├── __tests__ │ │ ├── branch-test.js │ │ ├── componentFromProp-test.js │ │ ├── componentFromStream-test.js │ │ ├── componentFromStreamWithConfig-test.js │ │ ├── compose-test.js │ │ ├── createEventHandler-test.js │ │ ├── createSink-test.js │ │ ├── defaultProps-test.js │ │ ├── fixtures │ │ │ └── treeshake-entry.js │ │ ├── flattenProp-test.js │ │ ├── fromRenderProps-test.js │ │ ├── getContext-test.js │ │ ├── getDisplayName-test.js │ │ ├── hoistStatics-test.js │ │ ├── isClassComponent-test.js │ │ ├── lifecycle-test.js │ │ ├── mapProps-test.js │ │ ├── mapPropsStream-test.js │ │ ├── mapPropsStreamWithConfig-test.js │ │ ├── nest-test.js │ │ ├── onlyUpdateForKeys-test.js │ │ ├── onlyUpdateForPropTypes-test.js │ │ ├── pure-test.js │ │ ├── renameProp-test.js │ │ ├── renameProps-test.js │ │ ├── renderComponent-test.js │ │ ├── renderNothing-test.js │ │ ├── setDisplayName-test.js │ │ ├── setObservableConfig-test.js │ │ ├── setPropTypes-test.js │ │ ├── setStatic-test.js │ │ ├── shallowEqual-test.js │ │ ├── shouldUpdate-test.js │ │ ├── toClass-test.js │ │ ├── toRenderProps-test.js │ │ ├── types │ │ │ ├── test_branch.js │ │ │ ├── test_classBasedEnhancer.js │ │ │ ├── test_componentFromStream.js │ │ │ ├── test_createEventHandler.js │ │ │ ├── test_defaultProps.js │ │ │ ├── test_fromRenderProps.js │ │ │ ├── test_functionalEnhancer.js │ │ │ ├── test_getContext.js │ │ │ ├── test_mapProps.js │ │ │ ├── test_mapPropsStream.js │ │ │ ├── test_onlyUpdateForKeys.js │ │ │ ├── test_onlyUpdateForPropTypes.js │ │ │ ├── test_pure.js │ │ │ ├── test_shouldUpdate.js │ │ │ ├── test_statics.js │ │ │ ├── test_toClass.js │ │ │ ├── test_toRenderProps.js │ │ │ ├── test_utils.js │ │ │ ├── test_voodoo.js │ │ │ ├── test_withContext.js │ │ │ ├── test_withHandlers.js │ │ │ ├── test_withProps.js │ │ │ ├── test_withPropsOnChange.js │ │ │ └── test_withStateHandlers.js │ │ ├── utils.js │ │ ├── withContext-test.js │ │ ├── withHandlers-test.js │ │ ├── withProps-test.js │ │ ├── withPropsOnChange-test.js │ │ ├── withReducer-test.js │ │ ├── withState-test.js │ │ ├── withStateHandlers-test.js │ │ └── wrapDisplayName-test.js │ ├── baconObservableConfig.js │ ├── branch.js │ ├── componentFromProp.js │ ├── componentFromStream.js │ ├── compose.js │ ├── createEventHandler.js │ ├── createSink.js │ ├── defaultProps.js │ ├── flattenProp.js │ ├── flydObservableConfig.js │ ├── fromRenderProps.js │ ├── getContext.js │ ├── getDisplayName.js │ ├── hoistStatics.js │ ├── index.js │ ├── index.js.flow │ ├── isClassComponent.js │ ├── kefirObservableConfig.js │ ├── lifecycle.js │ ├── mapProps.js │ ├── mapPropsStream.js │ ├── mostObservableConfig.js │ ├── nest.js │ ├── onlyUpdateForKeys.js │ ├── onlyUpdateForPropTypes.js │ ├── package.json │ ├── pure.js │ ├── renameProp.js │ ├── renameProps.js │ ├── renderComponent.js │ ├── renderNothing.js │ ├── rxjs4ObservableConfig.js │ ├── rxjsObservableConfig.js │ ├── setDisplayName.js │ ├── setObservableConfig.js │ ├── setPropTypes.js │ ├── setStatic.js │ ├── shallowEqual.js │ ├── shouldUpdate.js │ ├── toClass.js │ ├── toRenderProps.js │ ├── utils │ │ ├── mapValues.js │ │ ├── omit.js │ │ └── pick.js │ ├── withContext.js │ ├── withHandlers.js │ ├── withProps.js │ ├── withPropsOnChange.js │ ├── withReducer.js │ ├── withState.js │ ├── withStateHandlers.js │ ├── wrapDisplayName.js │ ├── xstreamObservableConfig.js │ └── yarn.lock │ └── rx-recompose │ └── yarn.lock ├── types ├── README.md └── flow-example │ ├── .eslintrc │ ├── .flowconfig │ ├── .gitignore │ ├── README.md │ ├── flow-typed │ ├── glamor.js │ └── react-motion.js │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.js │ ├── Item.js │ ├── ItemsAnimator.js │ ├── MouseDetector.js │ └── index.js │ └── yarn.lock └── yarn.lock /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - javascript 8 | eslint: 9 | enabled: true 10 | fixme: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - "**.js" 15 | exclude_paths: 16 | - "**/*-test.js" 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/types -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint-config-airbnb", 4 | "prettier", 5 | "prettier/flowtype", 6 | "prettier/react" 7 | ], 8 | "plugins": [ 9 | "prettier" 10 | ], 11 | "parser": "babel-eslint", 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "jest": true 16 | }, 17 | "globals": { 18 | "sinon": true 19 | }, 20 | "rules": { 21 | "semi": [2, "never"], 22 | "no-use-before-define": ["error", { "functions": false }], 23 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 24 | "no-underscore-dangle": [0], 25 | "no-confusing-arrow": [0], 26 | "no-class-assign": [0], 27 | "no-plusplus": [0], 28 | "no-prototype-builtins": [0], 29 | "no-return-assign": [0], 30 | "max-len": ["error", { "code": 120, "ignorePattern": "^test", "ignoreUrls": true }], 31 | "lines-between-class-members": [0], 32 | "prefer-destructuring": [0], 33 | "import/no-unresolved": [0], 34 | "import/no-extraneous-dependencies": [0], 35 | "import/extensions": [0], 36 | "import/prefer-default-export": [0], 37 | "import/no-useless-path-segments": [0], 38 | "jsx-a11y/label-has-for": [0], 39 | "react/forbid-prop-types": [0], 40 | "react/prop-types": [0], 41 | "react/prefer-stateless-function": [0], 42 | "react/no-multi-comp": [0], 43 | "react/sort-comp": [0], 44 | "react/jsx-filename-extension": [0], 45 | "prettier/prettier": ["error", {"semi": false, "trailingComma": "es5", "singleQuote": true}], 46 | "react/destructuring-assignment": [0], 47 | "react/jsx-curly-brace-presence": [0], 48 | "react/no-unused-prop-types": [0], 49 | "react/require-default-props": [0], 50 | "react/button-has-type": [0] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | <PROJECT_ROOT>/types/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError 10 | 11 | [lints] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | release 3 | lib 4 | coverage 5 | .vscode 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /**/__tests__ 2 | coverage 3 | types -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: es5 -------------------------------------------------------------------------------- /.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib/packages/recompose/dist/Recompose.umd.js": { 3 | "bundled": 46425, 4 | "minified": 16484, 5 | "gzipped": 4625 6 | }, 7 | "lib/packages/recompose/dist/Recompose.min.js": { 8 | "bundled": 42863, 9 | "minified": 15204, 10 | "gzipped": 4194 11 | }, 12 | "lib/packages/recompose/dist/Recompose.esm.js": { 13 | "bundled": 32428, 14 | "minified": 15083, 15 | "gzipped": 3550, 16 | "treeshaked": { 17 | "rollup": { 18 | "code": 310, 19 | "import_statements": 310 20 | }, 21 | "webpack": { 22 | "code": 1838 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | install: "yarn install --ignore-engines" 5 | before_script: "npm install -g codeclimate-test-reporter" 6 | script: 7 | - yarn run lint 8 | - yarn test 9 | after_script: 10 | - "CODECLIMATE_REPO_TOKEN=27125df6192d300baf67cd5f5eab6597c998256f4883b744a1055d0f0c18e608 codeclimate-test-reporter < coverage/lcov.info" 11 | - "cat ./coverage/lcov.info | $(npm bin)/codecov" 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | We are using the [Github Releases page](https://github.com/acdlite/recompose/releases) as our CHANGELOG. 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 Andrew Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [['@babel/proposal-class-properties', { loose: true }]], 3 | presets: [['@babel/env', { loose: true }], '@babel/react'], 4 | } 5 | 6 | if (process.env.NODE_ENV === 'cjs') { 7 | module.exports.plugins.push('@babel/transform-runtime') 8 | } 9 | -------------------------------------------------------------------------------- /docs/flow.md: -------------------------------------------------------------------------------- 1 | # Flow support for recompose 2 | 3 | ## How it works 4 | 5 | In most cases all you need is to declare a props type of enhanced Component. 6 | Flow will infer all other types you need. 7 | 8 | Example: 9 | 10 | ```javascript 11 | import type { HOC } from 'recompose'; 12 | 13 | type EnhancedComponentProps = { 14 | text?: string, 15 | }; 16 | 17 | const baseComponent = ({ text }) => <div>{text}</div>; 18 | 19 | const enhance:HOC<*, EnhancedComponentProps> = compose( 20 | defaultProps({ 21 | text: 'world', 22 | }), 23 | withProps(({ text }) => ({ 24 | text: `Hello ${text}` 25 | })) 26 | ); 27 | 28 | export default enhance(baseComponent); 29 | 30 | ``` 31 | 32 | See it in action. 33 | 34 |  35 | 36 | ## How to start 37 | 38 | The easiest way is to start from example. 39 | 40 | Look at [this](http://grader-meets-16837.netlify.com/) app [source](../types/flow-example) 41 | 42 | 43 | ## Support 44 | 45 | Type definitions of recompose HOCs are splitted into 2 parts. 46 | 47 | ### Part 1 - HOCs with good flow support 48 | 49 | In most cases you can use them without big issues. 50 | Type inference and errors detection works near well. 51 | 52 | These HOCs are: *defaultProps, mapProps, withProps, withStateHandlers, withHandlers, pure, onlyUpdateForKeys, shouldUpdate, renderNothing, renderComponent, branch, withPropsOnChange, onlyUpdateForPropTypes, toClass, withContext, getContext, setStatic, setPropTypes, setDisplayName* 53 | 54 | #### Known issues for "good" HOCs 55 | 56 | see `test_mapProps.js` - inference work but type errors are not detected in hocs 57 | 58 | ### Part 2 - other HOCs 59 | 60 | To use these HOCs - you need to provide type information (no automatic type inference). 61 | You must be a good voodoo dancer. 62 | 63 | See `test_voodoo.js` for the idea. 64 | 65 | Some recomendations: 66 | 67 | - *flattenProp,renameProp, renameProps* can easily be replaced with _withProps_ 68 | - *withReducer, withState* -> use _withStateHandlers_ instead 69 | - _lifecycle_ -> you don't need recompose if you need a _lifecycle_, just use React class instead 70 | - _mapPropsStream_ -> see `test_mapPropsStream.js` 71 | 72 | #### Known issues for above HOCs 73 | 74 | See `test_voodoo.js`, `test_mapPropsStream.js` 75 | 76 | ### Utils 77 | 78 | *getDisplayName, wrapDisplayName, shallowEqual,isClassComponent, createSink, componentFromProp, nest, hoistStatics.* 79 | 80 | ### Articles 81 | 82 | [Typing Higher-order Components in Recompose With Flow](https://medium.com/flow-type/flow-support-in-recompose-1b76f58f4cfc) 83 | 84 | ### Faq 85 | 86 | Why to use existential type with `HOC<*, Blbla>` isn't it possible to avoid this? 87 | 88 | *I tried to use type alias but haven't found how to make it work.* 89 | 90 | ## Thanks 91 | 92 | Big thanks to [@gcanti](https://github.com/gcanti) for his work on PR [#241](https://github.com/acdlite/recompose/pull/241), it was nice and clear base for current definitions. 93 | -------------------------------------------------------------------------------- /docs/performance.md: -------------------------------------------------------------------------------- 1 | # Should I use this? Performance and other concerns 2 | 3 | If function composition doesn't scare you, then yes, I think so. I believe using higher-order component helpers leads to smaller, more focused components, and provides a better programming model than using classes for operations—like `mapProps()` or `shouldUpdate()`—that aren't inherently class-y. 4 | 5 | That being said, any abstraction over an existing API is going to come with trade-offs. There is a performance overhead when introducing a new component to the tree. I suspect this cost is negligible compared to the gains achieved by blocking subtrees from re-rendering using `shouldComponentUpdate()`—which Recompose makes easy with its `shouldUpdate()` and `onlyUpdateForKeys()` helpers. In the future, I'll work on some benchmarks so we know what we're dealing with. 6 | 7 | However, many of Recompose's higher-order component helpers are implemented using stateless function components rather than class components. Eventually, React will include optimizations for stateless components. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recompose-build", 3 | "private": true, 4 | "author": "Andrew Clark <acdlite@me.com>", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/acdlite/recompose.git" 8 | }, 9 | "license": "MIT", 10 | "scripts": { 11 | "lint": "eslint scripts src", 12 | "build:recompose": "cross-env PACKAGE_NAME=recompose rollup --config scripts/rollup.config.js", 13 | "test": "jest && flow check && cross-env SNAPSHOT=match npm run build:recompose", 14 | "test:watch": "cross-env BABEL_ENV=cjs jest --watch", 15 | "release": "node scripts/release.js", 16 | "postinstall": "node scripts/installNestedPackageDeps.js", 17 | "format": "prettier --semi false --trailing-comma es5 --single-quote --write 'scripts/*.js' 'src/packages/*/*.js' 'src/packages/*/!(node_modules)/**/*.js'", 18 | "precommit": "lint-staged", 19 | "prepush": "yarn test" 20 | }, 21 | "jest": { 22 | "testMatch": [ 23 | "<rootDir>/src/**/__tests__/**/*-test.js" 24 | ], 25 | "coverageReporters": [ 26 | "text-summary", 27 | "lcov" 28 | ], 29 | "setupTestFrameworkScriptFile": "<rootDir>/scripts/jest.setup.js" 30 | }, 31 | "lint-staged": { 32 | "*.js": [ 33 | "prettier --semi false --trailing-comma es5 --single-quote --write", 34 | "eslint --fix", 35 | "git add" 36 | ] 37 | }, 38 | "devDependencies": { 39 | "@babel/cli": "^7.0.0", 40 | "@babel/core": "^7.0.0", 41 | "@babel/plugin-proposal-class-properties": "^7.0.0", 42 | "@babel/plugin-transform-runtime": "^7.0.0", 43 | "@babel/preset-env": "^7.0.0", 44 | "@babel/preset-react": "^7.0.0", 45 | "@babel/runtime": "^7.0.0", 46 | "babel-core": "^7.0.0-bridge.0", 47 | "babel-eslint": "^9.0.0", 48 | "babel-jest": "^22.4.3", 49 | "baconjs": "^0.7.84", 50 | "chalk": "^1.1.1", 51 | "change-case": "^2.3.1", 52 | "codecov": "^1.0.1", 53 | "create-react-class": "^15.5.0", 54 | "cross-env": "^4.0.0", 55 | "enzyme": "^3.3.0", 56 | "eslint": "^5.3.0", 57 | "eslint-config-airbnb": "^17.0.0", 58 | "eslint-config-prettier": "^2.9.0", 59 | "eslint-plugin-import": "^2.13.0", 60 | "eslint-plugin-jsx-a11y": "^6.1.1", 61 | "eslint-plugin-prettier": "^2.0.1", 62 | "eslint-plugin-react": "^7.10.0", 63 | "flow-bin": "^0.72.0", 64 | "flyd": "^0.2.4", 65 | "husky": "^0.13.3", 66 | "jest": "^22.4.3", 67 | "kefir": "^3.2.3", 68 | "lint-staged": "^3.4.0", 69 | "most": "^1.0.2", 70 | "prettier": "^1.2.2", 71 | "prop-types": "^15.6.1", 72 | "react": "^16.3.1", 73 | "react-dom": "^16.3.1", 74 | "readline-sync": "^1.2.21", 75 | "rollup": "^0.65.0", 76 | "rollup-plugin-babel": "^4.0.1", 77 | "rollup-plugin-commonjs": "^9.1.6", 78 | "rollup-plugin-node-resolve": "^3.3.0", 79 | "rollup-plugin-replace": "^2.0.0", 80 | "rollup-plugin-size-snapshot": "^0.6.1", 81 | "rollup-plugin-uglify": "^4.0.0", 82 | "rx": "^4.1.0", 83 | "rxjs": "^5.0.0", 84 | "shelljs": "^0.6.0", 85 | "sinon": "^1.17.1", 86 | "webpack": "^2.4.1", 87 | "xstream": "^5.0.5" 88 | }, 89 | "devEngines": { 90 | "node": "5.x", 91 | "npm": "3.x" 92 | }, 93 | "dependencies": { 94 | "enzyme-adapter-react-16": "^1.1.1" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripts/getPackageNames.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | exports.PACKAGES_SRC_DIR = './src/packages' 5 | exports.PACKAGES_OUT_DIR = './lib/packages' 6 | 7 | let names 8 | 9 | exports.getPackageNames = () => { 10 | if (!names) { 11 | names = fs.readdirSync(exports.PACKAGES_SRC_DIR).filter(file => { 12 | try { 13 | const packageJsonPath = path.resolve( 14 | exports.PACKAGES_SRC_DIR, 15 | file, 16 | 'package.json' 17 | ) 18 | return fs.statSync(packageJsonPath).isFile() 19 | } catch (error) { 20 | return false 21 | } 22 | }) 23 | } 24 | return names 25 | } 26 | -------------------------------------------------------------------------------- /scripts/installNestedPackageDeps.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { exec } = require('shelljs') 3 | const { getPackageNames, PACKAGES_SRC_DIR } = require('./getPackageNames.js') 4 | 5 | const packageNames = getPackageNames() 6 | 7 | packageNames.forEach(packageName => { 8 | const sourceDir = path.resolve(PACKAGES_SRC_DIR, packageName) 9 | exec(`cd ${sourceDir} && yarn`, { async: true }) 10 | }) 11 | -------------------------------------------------------------------------------- /scripts/jest.setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000 3 | 4 | import Enzyme from 'enzyme' 5 | import Adapter from 'enzyme-adapter-react-16' 6 | 7 | Enzyme.configure({ adapter: new Adapter() }) 8 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint-disable import/no-dynamic-require, no-console */ 3 | const fs = require('fs') 4 | const path = require('path') 5 | const { exec, exit, rm, cp, test } = require('shelljs') 6 | const chalk = require('chalk') 7 | const { flowRight: compose } = require('lodash') 8 | const readline = require('readline-sync') 9 | const semver = require('semver') 10 | const { pascalCase } = require('change-case') 11 | 12 | const BIN = './node_modules/.bin' 13 | 14 | const { 15 | PACKAGES_SRC_DIR, 16 | PACKAGES_OUT_DIR, 17 | getPackageNames, 18 | } = require('./getPackageNames') 19 | 20 | const BASE_PACKAGE_LOC = '../src/basePackage.json' 21 | 22 | const consoleLog = console.log.bind(console) 23 | const log = compose(consoleLog, chalk.bold) 24 | const logSuccess = compose(consoleLog, chalk.green.bold) 25 | const logError = compose(consoleLog, chalk.red.bold) 26 | 27 | const writeFile = (filepath, string) => 28 | fs.writeFileSync(filepath, string, 'utf8') 29 | 30 | try { 31 | if (exec('git diff-files --quiet').code !== 0) { 32 | logError( 33 | 'You have unsaved changes in the working tree. ' + 34 | 'Commit or stash changes before releasing.' 35 | ) 36 | exit(1) 37 | } 38 | 39 | const packageNames = getPackageNames() 40 | 41 | let packageName = readline.question('Name of package to release: ') 42 | 43 | while (!packageNames.includes(packageName)) { 44 | packageName = readline.question( 45 | `The package "${packageName}" does not exist in this project. ` + 46 | 'Choose again: ' 47 | ) 48 | } 49 | 50 | const libraryName = pascalCase(packageName) 51 | 52 | const versionLoc = path.resolve(PACKAGES_SRC_DIR, packageName, 'VERSION') 53 | const version = fs.readFileSync(versionLoc, 'utf8').trim() 54 | 55 | let nextVersion = readline.question( 56 | `Next version of ${packageName} (current version is ${version}): ` 57 | ) 58 | 59 | while ( 60 | !( 61 | !nextVersion || 62 | (semver.valid(nextVersion) && semver.gt(nextVersion, version)) 63 | ) 64 | ) { 65 | nextVersion = readline.question( 66 | `Must provide a valid version that is greater than ${version}, ` + 67 | 'or leave blank to skip: ' 68 | ) 69 | } 70 | 71 | log('Running tests...') 72 | 73 | if (exec('yarn run lint && yarn test').code !== 0) { 74 | logError('The test command did not exit cleanly. Aborting release.') 75 | exit(1) 76 | } 77 | 78 | logSuccess('Tests were successful.') 79 | 80 | const sourceDir = path.resolve(PACKAGES_SRC_DIR, packageName) 81 | const outDir = path.resolve(PACKAGES_OUT_DIR, packageName) 82 | 83 | log('Cleaning destination directory...') 84 | rm('-rf', outDir) 85 | 86 | log('Compiling source files...') 87 | 88 | exec( 89 | 'cross-env NODE_ENV=cjs ' + 90 | `${path.resolve(BIN)}/babel ${sourceDir} ` + 91 | `--out-dir ${path.resolve( 92 | outDir 93 | )} --ignore="**/__tests__/**,**/node_modules/**"` 94 | ) 95 | 96 | log('Copying additional project files...') 97 | const additionalProjectFiles = ['README.md', '.npmignore'] 98 | additionalProjectFiles.forEach(filename => { 99 | const src = path.resolve(sourceDir, filename) 100 | 101 | if (!test('-e', src)) return 102 | 103 | cp('-Rf', src, outDir) 104 | }) 105 | 106 | log('Generating package.json...') 107 | const packageConfig = Object.assign( 108 | { name: packageName, version: nextVersion }, 109 | require(BASE_PACKAGE_LOC), 110 | require(path.resolve(sourceDir, 'package.json')) 111 | ) 112 | 113 | writeFile( 114 | path.resolve(outDir, 'package.json'), 115 | JSON.stringify(packageConfig, null, 2) 116 | ) 117 | 118 | log('Copying license...') 119 | cp('-f', 'LICENSE.md', outDir) 120 | 121 | log(`Building ${packageName}...`) 122 | const runRollup = () => `yarn build:${packageName}` 123 | if (exec(runRollup()).code !== 0) { 124 | exit(1) 125 | } 126 | 127 | log(`Preparing ${libraryName}.cjs.js.flow...`) 128 | cp( 129 | '-f', 130 | path.resolve(sourceDir, 'index.js.flow'), 131 | path.resolve(outDir, 'dist', `${libraryName}.cjs.js.flow`) 132 | ) 133 | 134 | log(`About to publish ${packageName}@${nextVersion} to npm.`) 135 | if (!readline.keyInYN('Sound good? ')) { 136 | log('OK. Stopping release.') 137 | exit(0) 138 | } 139 | 140 | log('Publishing...') 141 | if (exec(`cd ${outDir} && npm publish`).code !== 0) { 142 | logError('Publish failed. Aborting release.') 143 | exit(1) 144 | } 145 | 146 | logSuccess(`${packageName}@${nextVersion} was successfully published.`) 147 | 148 | log('Updating VERSION file...') 149 | writeFile(versionLoc, `${nextVersion}\n`) 150 | 151 | log('Committing changes...') 152 | const newTagName = `v${nextVersion}` 153 | exec(`git add ${versionLoc}`) 154 | exec(`git commit -m "${packageName} ${newTagName}"`) 155 | 156 | if (packageName === 'recompose') { 157 | log(`Tagging release... (${newTagName})`) 158 | exec(`git tag ${newTagName}`) 159 | } 160 | 161 | log('Pushing to GitHub...') 162 | exec('git push') 163 | exec('git push --tags') 164 | 165 | logSuccess('Done.') 166 | } catch (error) { 167 | logError('Release failed due to an error', error) 168 | } 169 | -------------------------------------------------------------------------------- /scripts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import nodeResolve from 'rollup-plugin-node-resolve' 3 | import babel from 'rollup-plugin-babel' 4 | import replace from 'rollup-plugin-replace' 5 | import commonjs from 'rollup-plugin-commonjs' 6 | import { uglify } from 'rollup-plugin-uglify' 7 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot' 8 | import { pascalCase } from 'change-case' 9 | 10 | const { PACKAGES_SRC_DIR, PACKAGES_OUT_DIR } = require('./getPackageNames') 11 | 12 | const packageName = process.env.PACKAGE_NAME 13 | 14 | const libraryName = pascalCase(packageName) 15 | 16 | const input = `./${path.join(PACKAGES_SRC_DIR, packageName, 'index.js')}` 17 | 18 | const outDir = path.join(PACKAGES_OUT_DIR, packageName, 'dist') 19 | 20 | const isExternal = id => !id.startsWith('.') && !id.startsWith('/') 21 | 22 | const getBabelOptions = ({ useESModules }) => ({ 23 | exclude: '**/node_modules/**', 24 | runtimeHelpers: true, 25 | plugins: [['@babel/transform-runtime', { useESModules }]], 26 | }) 27 | 28 | const matchSnapshot = process.env.SNAPSHOT === 'match' 29 | 30 | export default [ 31 | { 32 | input, 33 | output: { 34 | file: `${outDir}/${libraryName}.umd.js`, 35 | format: 'umd', 36 | name: libraryName, 37 | globals: { 38 | react: 'React', 39 | }, 40 | }, 41 | external: ['react'], 42 | plugins: [ 43 | nodeResolve(), 44 | babel(getBabelOptions({ useESModules: true })), 45 | commonjs(), 46 | replace({ 'process.env.NODE_ENV': JSON.stringify('development') }), 47 | sizeSnapshot({ matchSnapshot }), 48 | ], 49 | }, 50 | 51 | { 52 | input, 53 | output: { 54 | file: `${outDir}/${libraryName}.min.js`, 55 | format: 'umd', 56 | name: libraryName, 57 | globals: { 58 | react: 'React', 59 | }, 60 | }, 61 | external: ['react'], 62 | plugins: [ 63 | nodeResolve(), 64 | babel(getBabelOptions({ useESModules: true })), 65 | commonjs(), 66 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 67 | sizeSnapshot({ matchSnapshot }), 68 | uglify(), 69 | ], 70 | }, 71 | 72 | { 73 | input, 74 | output: { 75 | file: `${outDir}/${libraryName}.cjs.js`, 76 | format: 'cjs', 77 | }, 78 | external: isExternal, 79 | plugins: [babel(getBabelOptions({ useESModules: false }))], 80 | }, 81 | 82 | { 83 | input, 84 | output: { 85 | file: `${outDir}/${libraryName}.esm.js`, 86 | format: 'es', 87 | }, 88 | external: isExternal, 89 | plugins: [ 90 | babel(getBabelOptions({ useESModules: true })), 91 | sizeSnapshot({ matchSnapshot }), 92 | ], 93 | }, 94 | ] 95 | -------------------------------------------------------------------------------- /src/basePackage.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrew Clark <acdlite@me.com>", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/acdlite/recompose.git" 6 | }, 7 | "license": "MIT", 8 | "bugs": { 9 | "url": "https://github.com/acdlite/recompose/issues" 10 | }, 11 | "homepage": "https://github.com/acdlite/recompose" 12 | } 13 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/.npmignore: -------------------------------------------------------------------------------- 1 | /**/__tests__ 2 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/README.md: -------------------------------------------------------------------------------- 1 | recompose-relay 2 | =============== 3 | 4 | [](https://www.npmjs.com/package/recompose-relay) 5 | 6 | [Recompose](https://github.com/acdlite/recompose) helpers for [Relay](https://facebook.github.io/relay). 7 | 8 | ``` 9 | npm install --save recompose-relay 10 | ``` 11 | 12 | ## API 13 | 14 | ### `createContainer()` 15 | 16 | ```js 17 | createContainer( 18 | specification: Object, 19 | BaseComponent: ReactElementType 20 | ): ReactElementType 21 | ``` 22 | 23 | A curried, component-last version of `Relay.createContainer()`. This makes it composable with other Recompose helpers. 24 | 25 | If the base component is not a class component, it is converted to one using `toClass()`. This allows Relay to add a ref to the base component without causing React to print a warning. (Function components cannot have refs.) This behavior will be removed once Relay updates to support function components. 26 | 27 | Tip: Use `flattenProp()` in combination with `createContainer()` to flatten fragment props: 28 | 29 | ```js 30 | const Post = compose( 31 | createContainer({ 32 | fragments: { 33 | post: () => Relay.QL` 34 | fragment on Post { 35 | title, 36 | content, 37 | author { 38 | name 39 | } 40 | } 41 | ` 42 | } 43 | }), 44 | flattenProp('post') 45 | )(({ title, content, author }) => ( 46 | <article> 47 | <h1>{title}</h1> 48 | <h2>By {author.name}</h2> 49 | <div>{content}</div> 50 | </article> 51 | )); 52 | ``` 53 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/VERSION: -------------------------------------------------------------------------------- 1 | 0.3.1 2 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/createContainer.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay' 2 | import { toClass } from 'recompose' 3 | 4 | const createContainer = options => BaseComponent => 5 | Relay.createContainer(toClass(BaseComponent), options) 6 | 7 | export default createContainer 8 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/index.js: -------------------------------------------------------------------------------- 1 | export { default as createContainer } from './createContainer' 2 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Recompose helpers for Relay.", 3 | "scripts": { 4 | "update-schema": "node -r babel-core/register updateSchema.js" 5 | }, 6 | "keywords": [ 7 | "recompose", 8 | "relay", 9 | "react", 10 | "higher-order", 11 | "components", 12 | "microcomponentization", 13 | "toolkit", 14 | "utilities", 15 | "composition" 16 | ], 17 | "main": "cjs/RecomposeRelay.js", 18 | "module": "es/RecomposeRelay.js", 19 | "dependencies": { 20 | "lodash": "^4.0.0" 21 | }, 22 | "peerDependencies": { 23 | "recompose": "^0.17.0 || ^0.18.0 || ^0.19.0 || ^0.20.0", 24 | "react": "^0.14.0 || ^15.0.0", 25 | "react-relay": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/packages/recompose-relay/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | lodash@^4.0.0: 6 | version "4.17.4" 7 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 8 | integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= 9 | -------------------------------------------------------------------------------- /src/packages/recompose/.npmignore: -------------------------------------------------------------------------------- 1 | /**/__tests__ 2 | -------------------------------------------------------------------------------- /src/packages/recompose/README.md: -------------------------------------------------------------------------------- 1 | recompose 2 | ========= 3 | 4 | [](https://www.npmjs.com/package/recompose) 5 | 6 | Recompose is a React utility belt for function components and higher-order components. See the [GitHub project page](https://github.com/acdlite/recompose) for more information. 7 | -------------------------------------------------------------------------------- /src/packages/recompose/VERSION: -------------------------------------------------------------------------------- 1 | 0.30.0 2 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/branch-test.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | import React from 'react' 3 | import { mount } from 'enzyme' 4 | import { branch, compose, withState, withProps } from '../' 5 | 6 | test('branch tests props and applies one of two HoCs, for true and false', () => { 7 | const SayMyName = compose( 8 | withState('isBad', 'updateIsBad', false), 9 | branch( 10 | props => props.isBad, 11 | withProps({ name: 'Heisenberg' }), 12 | withProps({ name: 'Walter' }) 13 | ) 14 | )(({ isBad, name, updateIsBad }) => 15 | <div> 16 | <div className="isBad"> 17 | {isBad ? 'true' : 'false'} 18 | </div> 19 | <div className="name"> 20 | {name} 21 | </div> 22 | <button onClick={() => updateIsBad(b => !b)}>Toggle</button> 23 | </div> 24 | ) 25 | 26 | expect(SayMyName.displayName).toBe('withState(branch(Component))') 27 | 28 | const wrapper = mount(<SayMyName />) 29 | const getIsBad = () => wrapper.find('.isBad').text() 30 | const getName = () => wrapper.find('.name').text() 31 | const toggle = wrapper.find('button') 32 | 33 | expect(getIsBad()).toBe('false') 34 | expect(getName()).toBe('Walter') 35 | 36 | toggle.simulate('click') 37 | 38 | expect(getIsBad()).toBe('true') 39 | expect(getName()).toBe('Heisenberg') 40 | }) 41 | 42 | test('branch defaults third argument to identity function', () => { 43 | const Left = () => <div className="left">Left</div> 44 | const Right = () => <div className="right">Right</div> 45 | 46 | const BranchedComponent = branch( 47 | () => false, 48 | () => props => <Left {...props} /> 49 | )(Right) 50 | 51 | const wrapper = mount(<BranchedComponent />) 52 | const right = wrapper.find('.right').text() 53 | 54 | expect(right).toBe('Right') 55 | }) 56 | 57 | test('branch third argument should not cause console error', () => { 58 | const error = sinon.stub(console, 'error') 59 | const Component = () => <div className="right">Component</div> 60 | 61 | const BranchedComponent = branch(() => false, v => v, v => v)(Component) 62 | 63 | mount(<BranchedComponent />) 64 | 65 | expect(error.called).toBe(false) 66 | 67 | /* eslint-disable */ 68 | error.restore() 69 | /* eslint-enable */ 70 | }) 71 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/componentFromProp-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { componentFromProp } from '../' 4 | 5 | test('componentFromProp creates a component that takes a component as a prop and renders it with the rest of the props', () => { 6 | const Container = componentFromProp('component') 7 | expect(Container.displayName).toBe('componentFromProp(component)') 8 | 9 | const Component = ({ pass }) => 10 | <div> 11 | Pass: {pass} 12 | </div> 13 | 14 | const wrapper = mount(<Container component={Component} pass="through" />) 15 | const div = wrapper.find('div') 16 | expect(div.text()).toBe('Pass: through') 17 | }) 18 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/componentFromStream-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { Observable, Subject } from 'rxjs' 4 | import sinon from 'sinon' 5 | import rxjsConfig from '../rxjsObservableConfig' 6 | import { componentFromStreamWithConfig } from '../componentFromStream' 7 | 8 | const componentFromStream = componentFromStreamWithConfig(rxjsConfig) 9 | 10 | test('componentFromStream creates a component from a prop stream transformation', () => { 11 | const Double = componentFromStream(props$ => 12 | props$.map(({ n }) => 13 | <div> 14 | {n * 2} 15 | </div> 16 | ) 17 | ) 18 | const wrapper = mount(<Double n={112} />) 19 | const div = wrapper.find('div') 20 | expect(div.text()).toBe('224') 21 | wrapper.setProps({ n: 358 }) 22 | expect(div.text()).toBe('716') 23 | }) 24 | 25 | test('componentFromStream unsubscribes from stream before unmounting', () => { 26 | let subscriptions = 0 27 | const vdom$ = new Observable(observer => { 28 | subscriptions += 1 29 | observer.next(<div />) 30 | return { 31 | unsubscribe() { 32 | subscriptions -= 1 33 | }, 34 | } 35 | }) 36 | const Div = componentFromStream(() => vdom$) 37 | const wrapper = mount(<Div />) 38 | expect(subscriptions).toBe(1) 39 | wrapper.unmount() 40 | expect(subscriptions).toBe(0) 41 | }) 42 | 43 | test('componentFromStream renders nothing until the stream emits a value', () => { 44 | const vdom$ = new Subject() 45 | const Div = componentFromStream(() => vdom$.mapTo(<div />)) 46 | const wrapper = mount(<Div />) 47 | expect(wrapper.find('div').length).toBe(0) 48 | vdom$.next() 49 | wrapper.update() 50 | expect(wrapper.find('div').length).toBe(1) 51 | }) 52 | 53 | test('handler multiple observers of props stream', () => { 54 | const Other = () => <div /> 55 | const Div = componentFromStream(props$ => 56 | // Adds three observers to props stream 57 | props$.combineLatest(props$, props$, props1 => <Other {...props1} />) 58 | ) 59 | 60 | const wrapper = mount(<Div data-value={1} />) 61 | const div = wrapper.find(Other) 62 | 63 | expect(div.prop('data-value')).toBe(1) 64 | wrapper.setProps({ 'data-value': 2 }) 65 | wrapper.update() 66 | const div2 = wrapper.find(Other) 67 | expect(div2.prop('data-value')).toBe(2) 68 | }) 69 | 70 | test('complete props stream before unmounting', () => { 71 | let counter = 0 72 | 73 | const Div = componentFromStream(props$ => { 74 | const first$ = props$.first().do(() => { 75 | counter += 1 76 | }) 77 | 78 | const last$ = props$ 79 | .last() 80 | .do(() => { 81 | counter -= 1 82 | }) 83 | .startWith(null) 84 | 85 | return props$.combineLatest(first$, last$, props1 => <div {...props1} />) 86 | }) 87 | 88 | const wrapper = mount(<Div />) 89 | 90 | expect(counter).toBe(1) 91 | expect(wrapper.find('div').length).toBe(1) 92 | 93 | wrapper.unmount() 94 | expect(counter).toBe(0) 95 | }) 96 | 97 | test('completed props stream should throw an exception', () => { 98 | const Div = componentFromStream(props$ => { 99 | const first$ = props$.filter(() => false).first().startWith(null) 100 | 101 | return props$.combineLatest(first$, props1 => <div {...props1} />) 102 | }) 103 | 104 | const wrapper = mount(<Div />) 105 | 106 | expect(wrapper.find('div').length).toBe(1) 107 | 108 | const error = sinon.stub(console, 'error') 109 | 110 | expect(() => wrapper.unmount()).toThrowError(/no elements in sequence/) 111 | expect(error.called).toBe(true) 112 | }) 113 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/componentFromStreamWithConfig-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { Observable } from 'rxjs' 4 | import { Stream as MostStream } from 'most' 5 | import mostConfig from '../mostObservableConfig' 6 | import rxjsConfig from '../rxjsObservableConfig' 7 | import { componentFromStreamWithConfig } from '../componentFromStream' 8 | 9 | test('componentFromStreamWithConfig creates a stream with the correct stream type.', () => { 10 | const MostComponent = componentFromStreamWithConfig(mostConfig)(props$ => { 11 | expect(props$ instanceof MostStream).toBe(true) 12 | return props$.map(v => 13 | <div> 14 | {String(v)} 15 | </div> 16 | ) 17 | }) 18 | 19 | mount(<MostComponent />) 20 | 21 | const RXJSComponent = componentFromStreamWithConfig(rxjsConfig)(props$ => { 22 | expect(props$ instanceof Observable).toBe(true) 23 | return props$.map(v => 24 | <div> 25 | {String(v)} 26 | </div> 27 | ) 28 | }) 29 | 30 | mount(<RXJSComponent />) 31 | }) 32 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/compose-test.js: -------------------------------------------------------------------------------- 1 | import { compose } from '../' 2 | 3 | test('compose composes from right to left', () => { 4 | const double = x => x * 2 5 | const square = x => x * x 6 | expect(compose(square)(5)).toBe(25) 7 | expect(compose(square, double)(5)).toBe(100) 8 | expect(compose(double, square, double)(5)).toBe(200) 9 | }) 10 | 11 | test('compose can be seeded with multiple arguments', () => { 12 | const square = x => x * x 13 | const add = (x, y) => x + y 14 | expect(compose(square, add)(1, 2)).toBe(9) 15 | }) 16 | 17 | test('compose returns the identity function if given no arguments', () => { 18 | expect(compose()(1, 2)).toBe(1) 19 | expect(compose()(3)).toBe(3) 20 | expect(compose()()).toBe(undefined) 21 | }) 22 | 23 | test('compose returns the first function if given only one', () => { 24 | const fn = x => x * x 25 | expect(compose(fn)(3)).toBe(fn(3)) 26 | }) 27 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/createEventHandler-test.js: -------------------------------------------------------------------------------- 1 | import { createEventHandler } from '../' 2 | 3 | test('createEventHandler creates an event handler and a corresponding stream', () => { 4 | const result = [] 5 | const { stream, handler } = createEventHandler() 6 | const subscription = stream.subscribe({ next: v => result.push(v) }) 7 | 8 | handler(1) 9 | handler(2) 10 | handler(3) 11 | 12 | subscription.unsubscribe() 13 | expect(result).toEqual([1, 2, 3]) 14 | }) 15 | 16 | test('handles multiple subscribers', () => { 17 | const result1 = [] 18 | const result2 = [] 19 | const { handler, stream } = createEventHandler() 20 | const subscription1 = stream.subscribe({ next: v => result1.push(v) }) 21 | const subscription2 = stream.subscribe({ next: v => result2.push(v) }) 22 | 23 | handler(1) 24 | handler(2) 25 | handler(3) 26 | 27 | subscription1.unsubscribe() 28 | subscription2.unsubscribe() 29 | 30 | expect(result1).toEqual([1, 2, 3]) 31 | expect(result2).toEqual([1, 2, 3]) 32 | }) 33 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/createSink-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { createSink, compose, withState, mapProps } from '../' 5 | 6 | test('createSink creates a React component that fires a callback when receiving new props', () => { 7 | const spy = sinon.spy() 8 | const Sink = createSink(spy) 9 | const Counter = compose( 10 | withState('counter', 'updateCounter', 0), 11 | mapProps(({ updateCounter, ...rest }) => ({ 12 | increment: () => updateCounter(n => n + 1), 13 | ...rest, 14 | })) 15 | )(Sink) 16 | 17 | mount( 18 | <div> 19 | <Counter /> 20 | </div> 21 | ) 22 | 23 | const { increment } = spy.lastCall.args[0] 24 | const getCounter = () => spy.lastCall.args[0].counter 25 | expect(getCounter()).toBe(0) 26 | increment() 27 | expect(getCounter()).toBe(1) 28 | increment() 29 | expect(getCounter()).toBe(2) 30 | }) 31 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/defaultProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { defaultProps } from '../' 4 | 5 | test('defaultProps passes additional props to base component', () => { 6 | const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') 7 | expect(DoReMi.displayName).toBe('defaultProps(div)') 8 | 9 | const div = shallow(<DoReMi />).find('div') 10 | expect(div.equals(<div data-so="do" data-la="fa" />)).toBe(true) 11 | }) 12 | 13 | test('defaultProps has lower precendence than props from owner', () => { 14 | const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') 15 | expect(DoReMi.displayName).toBe('defaultProps(div)') 16 | 17 | const div = shallow(<DoReMi data-la="ti" />).find('div') 18 | expect(div.equals(<div data-so="do" data-la="ti" />)).toBe(true) 19 | }) 20 | 21 | test('defaultProps overrides undefined owner props', () => { 22 | const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') 23 | expect(DoReMi.displayName).toBe('defaultProps(div)') 24 | 25 | const div = shallow(<DoReMi data-la={undefined} />).find('div') 26 | expect(div.equals(<div data-so="do" data-la="fa" />)).toBe(true) 27 | }) 28 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/fixtures/treeshake-entry.js: -------------------------------------------------------------------------------- 1 | import '../../../../../lib/packages/recompose/es/Recompose.js' 2 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/flattenProp-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { flattenProp } from '../' 4 | 5 | test('flattenProps flattens an object prop and spreads it into the top-level props object', () => { 6 | const Counter = flattenProp('data-state')('div') 7 | expect(Counter.displayName).toBe('flattenProp(div)') 8 | 9 | const wrapper = shallow( 10 | <Counter data-pass="through" data-state={{ 'data-counter': 1 }} /> 11 | ) 12 | 13 | expect( 14 | wrapper.equals( 15 | <div 16 | data-pass="through" 17 | data-state={{ 'data-counter': 1 }} 18 | data-counter={1} 19 | /> 20 | ) 21 | ).toBe(true) 22 | 23 | wrapper.setProps({ 24 | 'data-pass': 'through', 25 | 'data-state': { 'data-state': 1 }, 26 | }) 27 | expect(wrapper.equals(<div data-pass="through" data-state={1} />)).toBe(true) 28 | }) 29 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/fromRenderProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { fromRenderProps, compose, toRenderProps, defaultProps } from '../' 4 | 5 | test('fromRenderProps passes additional props to base component', () => { 6 | const RenderPropsComponent = ({ children }) => children({ i18n: 'zh-TW' }) 7 | const EnhancedComponent = fromRenderProps( 8 | RenderPropsComponent, 9 | ({ i18n }) => ({ 10 | i18n, 11 | }) 12 | )('div') 13 | expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') 14 | 15 | const div = mount(<EnhancedComponent />) 16 | expect(div.html()).toBe(`<div i18n="zh-TW"></div>`) 17 | }) 18 | 19 | test('fromRenderProps passes additional props to base component with custom renderPropName', () => { 20 | const RenderPropsComponent = ({ render }) => render({ i18n: 'zh-TW' }) 21 | const EnhancedComponent = fromRenderProps( 22 | RenderPropsComponent, 23 | ({ i18n }) => ({ 24 | i18n, 25 | }), 26 | 'render' 27 | )('div') 28 | expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') 29 | 30 | const div = mount(<EnhancedComponent />) 31 | expect(div.html()).toBe(`<div i18n="zh-TW"></div>`) 32 | }) 33 | 34 | test('fromRenderProps passes additional props to base component with 2 RenderPropsComponents', () => { 35 | const RenderPropsComponent1 = ({ children }) => children({ theme: 'dark' }) 36 | const RenderPropsComponent2 = ({ render }) => render({ i18n: 'zh-TW' }) 37 | const EnhancedComponent = compose( 38 | fromRenderProps( 39 | RenderPropsComponent1, 40 | ({ theme }) => ({ theme }), 41 | 'children' 42 | ), 43 | fromRenderProps( 44 | RenderPropsComponent2, 45 | ({ i18n }) => ({ locale: i18n }), 46 | 'render' 47 | ) 48 | )('div') 49 | expect(EnhancedComponent.displayName).toBe( 50 | 'fromRenderProps(fromRenderProps(div))' 51 | ) 52 | 53 | const div = mount(<EnhancedComponent />) 54 | expect(div.html()).toBe(`<div theme="dark" locale="zh-TW"></div>`) 55 | }) 56 | 57 | test('fromRenderProps meet toRenderProps', () => { 58 | const RenderPropsComponent = toRenderProps( 59 | defaultProps({ foo1: 'bar1', foo2: 'bar2' }) 60 | ) 61 | 62 | const EnhancedComponent = fromRenderProps( 63 | RenderPropsComponent, 64 | ({ foo1 }) => ({ 65 | foo: foo1, 66 | }) 67 | )('div') 68 | expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') 69 | 70 | const div = mount(<EnhancedComponent />) 71 | expect(div.html()).toBe(`<div foo="bar1"></div>`) 72 | }) 73 | 74 | test('fromRenderProps with multiple arguments #693', () => { 75 | const RenderPropsComponent = ({ children }) => 76 | children({ theme: 'dark' }, { data: 'data' }) 77 | const EnhancedComponent = compose( 78 | fromRenderProps( 79 | RenderPropsComponent, 80 | ({ theme }, { data }) => ({ theme, data }), 81 | 'children' 82 | ) 83 | )('div') 84 | expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') 85 | 86 | const div = mount(<EnhancedComponent />) 87 | expect(div.html()).toBe(`<div theme="dark" data="data"></div>`) 88 | }) 89 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/getContext-test.js: -------------------------------------------------------------------------------- 1 | // Tests for getContext() are covered by withContext-test.js 2 | // This is just a dummy test so Ava doesn't complain 3 | test('getContext works', () => expect(true).toBe(true)) 4 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/getDisplayName-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { getDisplayName } from '../' 3 | 4 | test('getDisplayName gets the display name of a React component', () => { 5 | class SomeComponent extends React.Component { 6 | render() { 7 | return <div /> 8 | } 9 | } 10 | 11 | class SomeOtherComponent extends React.Component { 12 | static displayName = 'CustomDisplayName' 13 | render() { 14 | return <div /> 15 | } 16 | } 17 | 18 | function YetAnotherComponent() { 19 | return <div /> 20 | } 21 | 22 | expect(getDisplayName(SomeComponent)).toBe('SomeComponent') 23 | expect(getDisplayName(SomeOtherComponent)).toBe('CustomDisplayName') 24 | expect(getDisplayName(YetAnotherComponent)).toBe('YetAnotherComponent') 25 | expect(getDisplayName(() => <div />)).toBe('Component') 26 | expect(getDisplayName('div')).toBe('div') 27 | }) 28 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/hoistStatics-test.js: -------------------------------------------------------------------------------- 1 | import React, { createFactory } from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { hoistStatics, mapProps } from '../' 5 | 6 | test('copies non-React static properties from base component to new component', () => { 7 | const BaseComponent = sinon.spy(() => null) 8 | BaseComponent.foo = () => {} 9 | 10 | const EnhancedComponent = hoistStatics( 11 | mapProps(props => ({ n: props.n * 5 })) 12 | )(BaseComponent) 13 | 14 | expect(EnhancedComponent.foo).toBe(BaseComponent.foo) 15 | 16 | mount(<EnhancedComponent n={3} />) 17 | expect(BaseComponent.firstCall.args[0].n).toBe(15) 18 | }) 19 | 20 | test('does not copy blacklisted static properties to new component ', () => { 21 | const BaseComponent = sinon.spy(() => null) 22 | BaseComponent.foo = () => {} 23 | BaseComponent.bar = () => {} 24 | 25 | const EnhancedComponent = hoistStatics( 26 | comp => createFactory(comp), 27 | { bar: true } // Blacklist 28 | )(BaseComponent) 29 | 30 | expect(EnhancedComponent.foo).toBe(BaseComponent.foo) 31 | expect(EnhancedComponent.bar).toBe(undefined) 32 | }) 33 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/isClassComponent-test.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import createReactClass from 'create-react-class' 3 | import isClassComponent from '../isClassComponent' 4 | 5 | test('isClassComponent returns false for functions', () => { 6 | const Foo = () => <div /> 7 | 8 | expect(isClassComponent(Foo)).toBe(false) 9 | }) 10 | 11 | test('isClassComponent returns true for React component classes', () => { 12 | class Foo extends Component { 13 | render() { 14 | return <div /> 15 | } 16 | } 17 | 18 | /* eslint-disable react/prefer-es6-class */ 19 | const Bar = createReactClass({ 20 | render() { 21 | return <div /> 22 | }, 23 | }) 24 | /* eslint-enable react/prefer-es6-class */ 25 | 26 | expect(isClassComponent(Foo)).toBe(true) 27 | expect(isClassComponent(Bar)).toBe(true) 28 | }) 29 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/lifecycle-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { lifecycle } from '../' 4 | 5 | test('lifecycle is a higher-order component version of React.Component', () => { 6 | const enhance = lifecycle({ 7 | componentWillMount() { 8 | this.setState({ 'data-bar': 'baz' }) 9 | }, 10 | }) 11 | const Div = enhance('div') 12 | expect(Div.displayName).toBe('lifecycle(div)') 13 | 14 | const div = mount(<Div data-foo="bar" />).find('div') 15 | expect(div.prop('data-foo')).toBe('bar') 16 | expect(div.prop('data-bar')).toBe('baz') 17 | }) 18 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/mapProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { mapProps, withState, compose } from '../' 5 | 6 | test('mapProps maps owner props to child props', () => { 7 | const component = sinon.spy(() => null) 8 | component.displayName = 'component' 9 | 10 | const StringConcat = compose( 11 | withState('strings', 'updateStrings', ['do', 're', 'mi']), 12 | mapProps(({ strings, ...rest }) => ({ 13 | ...rest, 14 | string: strings.join(''), 15 | })) 16 | )(component) 17 | 18 | expect(StringConcat.displayName).toBe('withState(mapProps(component))') 19 | 20 | mount(<StringConcat />) 21 | const { updateStrings } = component.firstCall.args[0] 22 | updateStrings(strings => [...strings, 'fa']) 23 | 24 | expect(component.firstCall.args[0].string).toBe('doremi') 25 | expect(component.secondCall.args[0].string).toBe('doremifa') 26 | }) 27 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/mapPropsStream-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import setObservableConfig from '../setObservableConfig' 4 | import rxjs4Config from '../rxjs4ObservableConfig' 5 | import { mapPropsStream } from '../' 6 | 7 | setObservableConfig(rxjs4Config) 8 | 9 | // Most of mapPropsStream's functionality is covered by componentFromStream 10 | test('mapPropsStream creates a higher-order component from a stream', () => { 11 | const Double = mapPropsStream(props$ => 12 | props$.map(({ n }) => ({ children: n * 2 })) 13 | )('div') 14 | const wrapper = mount(<Double n={112} />) 15 | const div = wrapper.find('div') 16 | expect(div.text()).toBe('224') 17 | wrapper.setProps({ n: 358 }) 18 | expect(div.text()).toBe('716') 19 | }) 20 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/mapPropsStreamWithConfig-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { Stream as MostStream } from 'most' 4 | import { Observable } from 'rxjs' 5 | import { mapPropsStreamWithConfig } from '../' 6 | import rxConfig from '../rxjsObservableConfig' 7 | import mostConfig from '../mostObservableConfig' 8 | 9 | // Most of mapPropsStreamConfig's functionality is covered by componentFromStream 10 | test('mapPropsStreamWithConfig creates a higher-order component from a stream and a observable config', () => { 11 | const Double = mapPropsStreamWithConfig(rxConfig)(props$ => 12 | props$.map(({ n }) => ({ children: n * 2 })) 13 | )('div') 14 | const wrapper = mount(<Double n={112} />) 15 | const div = wrapper.find('div') 16 | expect(div.text()).toBe('224') 17 | wrapper.setProps({ n: 358 }) 18 | expect(div.text()).toBe('716') 19 | }) 20 | 21 | test('mapPropsStreamWithConfig creates a stream with the correct config', () => { 22 | const MostComponent = mapPropsStreamWithConfig(mostConfig)(props$ => { 23 | expect(props$ instanceof MostStream).toBe(true) 24 | return props$.map(v => v) 25 | })('div') 26 | 27 | mount(<MostComponent />) 28 | 29 | const RXJSComponent = mapPropsStreamWithConfig(rxConfig)(props$ => { 30 | expect(props$ instanceof Observable).toBe(true) 31 | return props$.map(v => v) 32 | })('div') 33 | 34 | mount(<RXJSComponent />) 35 | }) 36 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/nest-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { nest, setDisplayName, toClass } from '../' 4 | 5 | test('nest nests components from outer to inner', () => { 6 | const A = setDisplayName('A')(toClass('div')) 7 | const B = setDisplayName('B')(toClass('div')) 8 | const C = setDisplayName('C')(toClass('div')) 9 | 10 | const Nest = nest(A, B, C) 11 | 12 | expect(Nest.displayName).toBe('nest(A, B, C)') 13 | 14 | const wrapper = shallow(<Nest pass="through">Child</Nest>) 15 | 16 | expect( 17 | wrapper.equals( 18 | <A pass="through"> 19 | <B pass="through"> 20 | <C pass="through">Child</C> 21 | </B> 22 | </A> 23 | ) 24 | ).toBe(true) 25 | }) 26 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/onlyUpdateForKeys-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { onlyUpdateForKeys, compose, withState } from '../' 5 | 6 | test('onlyUpdateForKeys implements shouldComponentUpdate()', () => { 7 | const component = sinon.spy(() => null) 8 | component.displayName = 'component' 9 | 10 | const Counter = compose( 11 | withState('counter', 'updateCounter', 0), 12 | withState('foobar', 'updateFoobar', 'foobar'), 13 | onlyUpdateForKeys(['counter']) 14 | )(component) 15 | 16 | expect(Counter.displayName).toBe( 17 | 'withState(withState(onlyUpdateForKeys(component)))' 18 | ) 19 | 20 | mount(<Counter />) 21 | const { updateCounter, updateFoobar } = component.firstCall.args[0] 22 | 23 | expect(component.lastCall.args[0].counter).toBe(0) 24 | expect(component.lastCall.args[0].foobar).toBe('foobar') 25 | 26 | // Does not update 27 | updateFoobar('barbaz') 28 | expect(component.calledOnce).toBe(true) 29 | 30 | updateCounter(42) 31 | expect(component.calledTwice).toBe(true) 32 | expect(component.lastCall.args[0].counter).toBe(42) 33 | expect(component.lastCall.args[0].foobar).toBe('barbaz') 34 | }) 35 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/onlyUpdateForPropTypes-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import sinon from 'sinon' 4 | import { mount, shallow } from 'enzyme' 5 | import { onlyUpdateForPropTypes, compose, withState, setPropTypes } from '../' 6 | 7 | test('onlyUpdateForPropTypes only updates for props specified in propTypes', () => { 8 | const component = sinon.spy(() => null) 9 | component.displayName = 'component' 10 | 11 | const Counter = compose( 12 | withState('counter', 'updateCounter', 0), 13 | withState('foobar', 'updateFoobar', 'foobar'), 14 | onlyUpdateForPropTypes, 15 | setPropTypes({ counter: PropTypes.number }) 16 | )(component) 17 | 18 | expect(Counter.displayName).toBe( 19 | 'withState(withState(onlyUpdateForPropTypes(component)))' 20 | ) 21 | 22 | mount(<Counter />) 23 | const { updateCounter, updateFoobar } = component.firstCall.args[0] 24 | 25 | expect(component.lastCall.args[0].counter).toBe(0) 26 | expect(component.lastCall.args[0].foobar).toBe('foobar') 27 | 28 | // Does not update 29 | updateFoobar('barbaz') 30 | expect(component.calledOnce).toBe(true) 31 | 32 | updateCounter(42) 33 | expect(component.calledTwice).toBe(true) 34 | expect(component.lastCall.args[0].counter).toBe(42) 35 | expect(component.lastCall.args[0].foobar).toBe('barbaz') 36 | }) 37 | 38 | test('onlyUpdateForPropTypes warns if BaseComponent does not have any propTypes', () => { 39 | const error = sinon.stub(console, 'error') 40 | const ShouldWarn = onlyUpdateForPropTypes('div') 41 | 42 | shallow(<ShouldWarn />) 43 | 44 | expect(error.firstCall.args[0]).toBe( 45 | 'A component without any `propTypes` was passed to ' + 46 | '`onlyUpdateForPropTypes()`. Check the implementation of the component ' + 47 | 'with display name "div".' 48 | ) 49 | 50 | /* eslint-disable */ 51 | console.error.restore() 52 | /* eslint-enable */ 53 | }) 54 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/pure-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { pure, compose, withState } from '../' 5 | import { countRenders } from './utils' 6 | 7 | test('pure implements shouldComponentUpdate() using shallowEqual()', () => { 8 | const component = sinon.spy(() => null) 9 | component.displayName = 'component' 10 | 11 | const initialTodos = ['eat', 'drink', 'sleep'] 12 | const Todos = compose( 13 | withState('todos', 'updateTodos', initialTodos), 14 | pure, 15 | countRenders 16 | )(component) 17 | 18 | expect(Todos.displayName).toBe('withState(pure(countRenders(component)))') 19 | 20 | mount(<Todos />) 21 | const { updateTodos } = component.firstCall.args[0] 22 | 23 | expect(component.lastCall.args[0].todos).toBe(initialTodos) 24 | expect(component.lastCall.args[0].renderCount).toBe(1) 25 | 26 | // Does not re-render 27 | updateTodos(initialTodos) 28 | expect(component.calledOnce).toBe(true) 29 | 30 | updateTodos(todos => todos.slice(0, -1)) 31 | expect(component.calledTwice).toBe(true) 32 | expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink']) 33 | expect(component.lastCall.args[0].renderCount).toBe(2) 34 | }) 35 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/renameProp-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { withProps, renameProp, compose } from '../' 4 | 5 | test('renameProp renames a single prop', () => { 6 | const StringConcat = compose( 7 | withProps({ 'data-so': 123, 'data-la': 456 }), 8 | renameProp('data-so', 'data-do') 9 | )('div') 10 | 11 | expect(StringConcat.displayName).toBe('withProps(renameProp(div))') 12 | 13 | const div = mount(<StringConcat />).find('div') 14 | expect(div.props()).toEqual({ 'data-do': 123, 'data-la': 456 }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/renameProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { withProps, renameProps, compose } from '../' 4 | 5 | test('renameProps renames props', () => { 6 | const StringConcat = compose( 7 | withProps({ 'data-so': 123, 'data-la': 456 }), 8 | renameProps({ 'data-so': 'data-do', 'data-la': 'data-fa' }) 9 | )('div') 10 | 11 | expect(StringConcat.displayName).toBe('withProps(renameProps(div))') 12 | 13 | const div = mount(<StringConcat />).find('div') 14 | 15 | expect(div.prop('data-do')).toBe(123) 16 | expect(div.prop('data-fa')).toBe(456) 17 | }) 18 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/renderComponent-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { renderComponent, withState, compose, branch } from '../' 5 | 6 | test('renderComponent always renders the given component', () => { 7 | const componentA = sinon.spy(() => null) 8 | const componentB = sinon.spy(() => null) 9 | 10 | const Foobar = compose( 11 | withState('flip', 'updateFlip', false), 12 | branch( 13 | props => props.flip, 14 | renderComponent(componentA), 15 | renderComponent(componentB) 16 | ) 17 | )(null) 18 | 19 | mount(<Foobar />) 20 | const { updateFlip } = componentB.firstCall.args[0] 21 | 22 | expect(componentB.calledOnce).toBe(true) 23 | expect(componentA.notCalled).toBe(true) 24 | 25 | updateFlip(true) 26 | expect(componentB.calledOnce).toBe(true) 27 | expect(componentA.calledOnce).toBe(true) 28 | }) 29 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/renderNothing-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { renderNothing } from '../' 4 | 5 | test('renderNothing returns a component that renders null', () => { 6 | const Nothing = renderNothing('div') 7 | const wrapper = shallow(<Nothing />) 8 | 9 | const Parent = () => <Nothing /> 10 | const parentWrapper = shallow(<Parent />) 11 | 12 | expect(wrapper.type()).toBe(null) 13 | expect(parentWrapper.text()).toBe('<Nothing />') 14 | }) 15 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/setDisplayName-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { setDisplayName } from '../' 3 | 4 | test('setDisplayName sets a static property on the base component', () => { 5 | const BaseComponent = () => <div /> 6 | const NewComponent = setDisplayName('Foo')(BaseComponent) 7 | expect(NewComponent.displayName).toBe('Foo') 8 | }) 9 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/setObservableConfig-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import rxjs5Config from '../rxjsObservableConfig' 4 | import rxjs4Config from '../rxjs4ObservableConfig' 5 | import mostConfig from '../mostObservableConfig' 6 | import xstreamConfig from '../xstreamObservableConfig' 7 | import baconConfig from '../baconObservableConfig' 8 | import kefirConfig from '../kefirObservableConfig' 9 | import flydConfig from '../flydObservableConfig' 10 | import setObservableConfig from '../setObservableConfig' 11 | import componentFromStream from '../componentFromStream' 12 | 13 | const testTransform = transform => { 14 | const Double = componentFromStream(transform) 15 | const wrapper = mount(<Double n={112} />) 16 | const div = wrapper.find('div') 17 | expect(div.text()).toBe('224') 18 | wrapper.setProps({ n: 358 }) 19 | expect(div.text()).toBe('716') 20 | } 21 | 22 | test('works with RxJS 5', () => { 23 | setObservableConfig(rxjs5Config) 24 | testTransform(props$ => 25 | props$.map(({ n }) => 26 | <div> 27 | {n * 2} 28 | </div> 29 | ) 30 | ) 31 | }) 32 | 33 | test('works with RxJS 4', () => { 34 | setObservableConfig(rxjs4Config) 35 | testTransform(props$ => 36 | props$.map(({ n }) => 37 | <div> 38 | {n * 2} 39 | </div> 40 | ) 41 | ) 42 | }) 43 | 44 | test('works with most', () => { 45 | setObservableConfig(mostConfig) 46 | testTransform(props$ => 47 | props$.map(({ n }) => 48 | <div> 49 | {n * 2} 50 | </div> 51 | ) 52 | ) 53 | }) 54 | 55 | test('works with xstream', () => { 56 | setObservableConfig(xstreamConfig) 57 | testTransform(props$ => 58 | props$.map(({ n }) => 59 | <div> 60 | {n * 2} 61 | </div> 62 | ) 63 | ) 64 | }) 65 | 66 | test('works with bacon', () => { 67 | setObservableConfig(baconConfig) 68 | testTransform(props$ => 69 | props$.map(({ n }) => 70 | <div> 71 | {n * 2} 72 | </div> 73 | ) 74 | ) 75 | }) 76 | 77 | test('works with kefir', () => { 78 | setObservableConfig(kefirConfig) 79 | testTransform(props$ => 80 | props$.map(({ n }) => 81 | <div> 82 | {n * 2} 83 | </div> 84 | ) 85 | ) 86 | }) 87 | 88 | test('works with flyd', () => { 89 | setObservableConfig(flydConfig) 90 | testTransform(props$ => 91 | props$.map(({ n }) => 92 | <div> 93 | {n * 2} 94 | </div> 95 | ) 96 | ) 97 | }) 98 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/setPropTypes-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { setPropTypes } from '../' 4 | 5 | test('setPropTypes sets a static property on the base component', () => { 6 | const BaseComponent = () => <div /> 7 | const NewComponent = setPropTypes({ foo: PropTypes.object })(BaseComponent) 8 | 9 | expect(NewComponent.propTypes).toEqual({ 10 | foo: PropTypes.object, 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/setStatic-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { setStatic } from '../' 4 | 5 | test('setStatic sets a static property on the base component', () => { 6 | const BaseComponent = () => <div /> 7 | const NewComponent = setStatic('propTypes', { foo: PropTypes.object })( 8 | BaseComponent 9 | ) 10 | 11 | expect(NewComponent.propTypes).toEqual({ 12 | foo: PropTypes.object, 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/shallowEqual-test.js: -------------------------------------------------------------------------------- 1 | import { shallowEqual } from '../' 2 | 3 | // Adapted from https://github.com/rackt/react-redux/blob/master/test/utils/shallowEqual.spec.js 4 | test('shallowEqual returns true if arguments are equal, without comparing properties', () => { 5 | const throwOnAccess = { 6 | get foo() { 7 | throw new Error('Property was accessed') 8 | }, 9 | } 10 | expect(shallowEqual(throwOnAccess, throwOnAccess)).toBe(true) 11 | }) 12 | 13 | test('shallowEqual returns true if arguments fields are equal', () => { 14 | expect( 15 | shallowEqual({ a: 1, b: 2, c: undefined }, { a: 1, b: 2, c: undefined }) 16 | ).toBe(true) 17 | 18 | expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true) 19 | 20 | const o = {} 21 | expect(shallowEqual({ a: 1, b: 2, c: o }, { a: 1, b: 2, c: o })).toBe(true) 22 | }) 23 | 24 | test('shallowEqual returns false if either argument is null or undefined', () => { 25 | expect(shallowEqual(null, { a: 1, b: 2 })).toBe(false) 26 | expect(shallowEqual({ a: 1, b: 2 }, null)).toBe(false) 27 | }) 28 | 29 | test('shallowEqual returns false if first argument has too many keys', () => { 30 | expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).toBe(false) 31 | }) 32 | 33 | test('shallowEqual returns false if second argument has too many keys', () => { 34 | expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 })).toBe(false) 35 | }) 36 | 37 | test('shallowEqual returns false if arguments have different keys', () => { 38 | expect( 39 | shallowEqual({ a: 1, b: 2, c: undefined }, { a: 1, bb: 2, c: undefined }) 40 | ).toBe(false) 41 | }) 42 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/shouldUpdate-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { shouldUpdate, compose, withState } from '../' 5 | import { countRenders } from './utils' 6 | 7 | test('shouldUpdate implements shouldComponentUpdate', () => { 8 | const component = sinon.spy(() => null) 9 | component.displayName = 'component' 10 | 11 | const initialTodos = ['eat', 'drink', 'sleep'] 12 | const Todos = compose( 13 | withState('todos', 'updateTodos', initialTodos), 14 | shouldUpdate((props, nextProps) => props.todos !== nextProps.todos), 15 | countRenders 16 | )(component) 17 | 18 | expect(Todos.displayName).toBe( 19 | 'withState(shouldUpdate(countRenders(component)))' 20 | ) 21 | 22 | mount(<Todos />) 23 | const { updateTodos } = component.firstCall.args[0] 24 | 25 | expect(component.lastCall.args[0].todos).toBe(initialTodos) 26 | expect(component.lastCall.args[0].renderCount).toBe(1) 27 | 28 | // Does not re-render 29 | updateTodos(initialTodos) 30 | expect(component.calledOnce).toBe(true) 31 | 32 | updateTodos(todos => todos.slice(0, -1)) 33 | expect(component.calledTwice).toBe(true) 34 | expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink']) 35 | expect(component.lastCall.args[0].renderCount).toBe(2) 36 | }) 37 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/toClass-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { mount } from 'enzyme' 4 | import sinon from 'sinon' 5 | import { toClass, withContext, compose } from '../' 6 | 7 | test('toClass returns the base component if it is already a class', () => { 8 | class BaseComponent extends React.Component { 9 | render() { 10 | return <div /> 11 | } 12 | } 13 | 14 | const TestComponent = toClass(BaseComponent) 15 | expect(TestComponent).toBe(BaseComponent) 16 | }) 17 | 18 | test('toClass copies propTypes, displayName, contextTypes and defaultProps from base component', () => { 19 | const StatelessComponent = () => <div /> 20 | 21 | StatelessComponent.displayName = 'Stateless' 22 | StatelessComponent.propTypes = { foo: PropTypes.string } 23 | StatelessComponent.contextTypes = { bar: PropTypes.object } 24 | StatelessComponent.defaultProps = { foo: 'bar', fizz: 'buzz' } 25 | 26 | const TestComponent = toClass(StatelessComponent) 27 | 28 | expect(TestComponent.displayName).toBe('Stateless') 29 | expect(TestComponent.propTypes).toEqual({ foo: PropTypes.string }) 30 | expect(TestComponent.contextTypes).toEqual({ bar: PropTypes.object }) 31 | expect(TestComponent.defaultProps).toEqual({ foo: 'bar', fizz: 'buzz' }) 32 | }) 33 | 34 | test('toClass passes defaultProps correctly', () => { 35 | const StatelessComponent = sinon.spy(() => null) 36 | 37 | StatelessComponent.displayName = 'Stateless' 38 | StatelessComponent.propTypes = { foo: PropTypes.string } 39 | StatelessComponent.contextTypes = { bar: PropTypes.object } 40 | StatelessComponent.defaultProps = { foo: 'bar', fizz: 'buzz' } 41 | 42 | const TestComponent = toClass(StatelessComponent) 43 | 44 | mount(<TestComponent />) 45 | expect(StatelessComponent.lastCall.args[0].foo).toBe('bar') 46 | expect(StatelessComponent.lastCall.args[0].fizz).toBe('buzz') 47 | }) 48 | 49 | test('toClass passes context and props correctly', () => { 50 | const store = {} 51 | 52 | class Provider extends React.Component { 53 | static propTypes = { 54 | children: PropTypes.node, 55 | } 56 | 57 | render() { 58 | return this.props.children 59 | } 60 | } 61 | 62 | Provider = compose( 63 | withContext({ store: PropTypes.object }, props => ({ store: props.store })) 64 | )(Provider) 65 | 66 | const StatelessComponent = (props, context) => 67 | <div data-props={props} data-context={context} /> 68 | 69 | StatelessComponent.contextTypes = { store: PropTypes.object } 70 | 71 | const TestComponent = toClass(StatelessComponent) 72 | 73 | const div = mount( 74 | <Provider store={store}> 75 | <TestComponent fizz="fizzbuzz" /> 76 | </Provider> 77 | ).find('div') 78 | 79 | expect(div.prop('data-props').fizz).toBe('fizzbuzz') 80 | expect(div.prop('data-context').store).toBe(store) 81 | }) 82 | 83 | test('toClass works with strings (DOM components)', () => { 84 | const Div = toClass('div') 85 | const div = mount(<Div>Hello</Div>) 86 | expect(div.html()).toBe('<div>Hello</div>') 87 | }) 88 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/toRenderProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { toRenderProps, defaultProps } from '../' 4 | 5 | test('toRenderProps creates a component from defaultProps HOC', () => { 6 | const enhance = defaultProps({ foo: 'bar' }) 7 | const Enhanced = toRenderProps(enhance) 8 | 9 | expect(Enhanced.displayName).toBe('defaultProps(RenderPropsComponent)') 10 | 11 | const h1 = mount( 12 | <Enhanced> 13 | {({ foo }) => 14 | <h1> 15 | {foo} 16 | </h1>} 17 | </Enhanced> 18 | ).find('h1') 19 | 20 | expect(h1.html()).toBe(`<h1>bar</h1>`) 21 | }) 22 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_branch.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { 5 | compose, 6 | withProps, 7 | branch, 8 | renderNothing, 9 | renderComponent, 10 | onlyUpdateForKeys, 11 | } from '../..' 12 | 13 | import type { HOC } from '../..' 14 | 15 | type EnhancedCompProps = { eA: 1 } 16 | 17 | const Comp = ({ eA }) => 18 | <div> 19 | {(eA: number)} 20 | { 21 | // $ExpectError eA nor any nor string 22 | (eA: string) 23 | } 24 | </div> 25 | 26 | const enhacer: HOC<*, EnhancedCompProps> = compose( 27 | branch(({ eA }) => eA === 1, renderNothing), 28 | withProps(props => ({ 29 | eA: (props.eA: number), 30 | // $ExpectError eA nor any nor string 31 | eAErr: (props.eA: string), 32 | })), 33 | withProps(props => ({ 34 | // $ExpectError property not found 35 | err: props.iMNotExists, 36 | })) 37 | ) 38 | 39 | const enhacerLoading: HOC<*, EnhancedCompProps> = compose( 40 | branch(({ eA }) => eA === 1, renderComponent(p => <div>Loading</div>)), 41 | withProps(props => ({ 42 | eA: (props.eA: number), 43 | // $ExpectError eA nor any nor string 44 | eAErr: (props.eA: string), 45 | })) 46 | ) 47 | 48 | // can work with onlyUpdateForKeys 49 | const enhacerUpdating: HOC<*, EnhancedCompProps> = compose( 50 | branch(({ eA }) => eA === 1, onlyUpdateForKeys(['eA'])), 51 | withProps(props => ({ 52 | eA: (props.eA: number), 53 | // $ExpectError eA nor any nor string 54 | eAErr: (props.eA: string), 55 | })) 56 | ) 57 | 58 | // can infer withProps type 59 | const enhacerWithProps: HOC<*, EnhancedCompProps> = compose( 60 | branch(({ eA }) => eA === 1, withProps(props => ({ x: 1 }))), 61 | withProps(props => ({ 62 | eA: (props.eA: number), 63 | // $ExpectError eA nor any nor string 64 | eAErr: (props.eA: string), 65 | })) 66 | ) 67 | 68 | // can infer compose types 69 | const enhacerWithCompose: HOC<*, EnhancedCompProps> = compose( 70 | branch( 71 | ({ eA }) => eA === 1, 72 | compose( 73 | withProps(props => { 74 | // $ExpectError eA nor any nor string 75 | ;(props.eA: string) 76 | 77 | return { x: 1 } 78 | }), 79 | withProps(props => ({ y: 2 })) 80 | ) 81 | ), 82 | withProps(props => ({ 83 | // $ExpectError eA nor any nor string 84 | eAErr: (props.eA: string), 85 | // $ExpectError x nor any nor string 86 | xErr: (props.x: string), 87 | // $ExpectError y nor any nor string 88 | yErr: (props.y: string), 89 | })) 90 | ) 91 | 92 | const enhacerLeftRight: HOC<*, EnhancedCompProps> = compose( 93 | branch( 94 | ({ eA }) => eA === 1, 95 | renderComponent(p => <div>A</div>), 96 | renderComponent(p => <div>B</div>) 97 | ), 98 | withProps(props => ({ 99 | // $ExpectError eA nor any nor string 100 | eAErr: (props.eA: string), 101 | // $ExpectError x nor any nor string 102 | xErr: (props.x: string), 103 | // $ExpectError y nor any nor string 104 | yErr: (props.y: string), 105 | })) 106 | ) 107 | 108 | /* 109 | Wrong types of left right, this will cause an infinite recursion 110 | 111 | const enhacerLeftRight2: HOC<*, EnhancedCompProps> = compose( 112 | branch( 113 | ({ eA }) => eA === 1, 114 | renderComponent(p => <div>A</div>), 115 | withProps(props => ({ y: 2 })) 116 | ) 117 | ); 118 | */ 119 | 120 | const EnhancedComponent = enhacer(Comp) 121 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_classBasedEnhancer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import * as React from 'react' 3 | import { compose, withProps } from '../..' 4 | import type { HOC } from '../..' 5 | 6 | // Example of very dirty written fetcher enhancer 7 | function fetcher<Response: {}, Base: {}>( 8 | dest: string, 9 | nullRespType: ?Response 10 | ): HOC<{ ...$Exact<Base>, data?: Response }, Base> { 11 | return BaseComponent => 12 | class Fetcher extends React.Component<Base, { data?: Response }> { 13 | state = { data: undefined } 14 | componentDidMount() { 15 | fetch(dest) 16 | .then(r => r.json()) 17 | .then((data: Response) => this.setState({ data })) 18 | } 19 | render() { 20 | return <BaseComponent {...this.props} {...this.state} /> 21 | } 22 | } 23 | } 24 | // Enhanced Component props type 25 | type EnhancedCompProps = { b: number } 26 | // response type 27 | type FetchResponseType = { hello: string, world: number } 28 | 29 | // Now you can use it, let's check 30 | const enhancer: HOC<*, EnhancedCompProps> = compose( 31 | // pass response type via typed null 32 | fetcher('http://endpoint.ep', (null: ?FetchResponseType)), 33 | // see here fully typed data 34 | withProps(({ data }) => { 35 | if (data !== undefined) { 36 | return { 37 | h: (data.hello: string), 38 | // $ExpectError 39 | hE: (data.hello: number), 40 | } 41 | } 42 | 43 | return {} 44 | }) 45 | ) 46 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_componentFromStream.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import { componentFromStream } from '../..' 4 | 5 | // $ExpectError 6 | componentFromStream(1) 7 | 8 | // $ExpectError 9 | const result1: number = componentFromStream(() => React.createElement('div')) 10 | 11 | componentFromStream(a => a) 12 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_createEventHandler.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import { createEventHandler } from '../..' 4 | 5 | // $ExpectError 6 | createEventHandler(1) 7 | 8 | // $ExpectError 9 | const result1: number = createEventHandler() 10 | 11 | // $ExpectError 12 | const { stream1, handler1 } = createEventHandler() 13 | 14 | const { stream, handler } = createEventHandler() 15 | 16 | handler() 17 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_defaultProps.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, defaultProps } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ hello, eA }) => 11 | <div> 12 | {(hello: string)} 13 | {(eA: number)} 14 | { 15 | // $ExpectError eA nor any nor string 16 | (eA: string) 17 | } 18 | { 19 | // $ExpectError hello nor any nor number 20 | (hello: number) 21 | } 22 | </div> 23 | 24 | const enhacer: HOC<*, EnhancedCompProps> = compose( 25 | defaultProps({ 26 | hello: 'world', 27 | }), 28 | withProps(props => ({ 29 | hello: (props.hello: string), 30 | eA: (props.eA: number), 31 | // $ExpectError hello nor any nor number 32 | helloErr: (props.hello: number), 33 | // $ExpectError eA nor any nor string 34 | eAErr: (props.eA: string), 35 | })), 36 | withProps(props => ({ 37 | // $ExpectError property not found 38 | err: props.iMNotExists, 39 | })) 40 | ) 41 | 42 | const EnhancedComponent = enhacer(Comp) 43 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_fromRenderProps.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { compose, fromRenderProps } from '../..' 4 | 5 | import type { HOC } from '../..' 6 | 7 | const RenderPropsComponent1 = ({ children }) => children({ theme: 'dark' }) 8 | const RenderPropsComponent2 = ({ render }) => render({ i18n: 'zh-TW' }) 9 | const RenderPropsComponent3 = ({ children }) => 10 | children({ theme: 'dark' }, { data: 'data' }) 11 | 12 | type EnhancedCompProps = {||} 13 | 14 | const Comp = ({ i18n, theme, data }) => 15 | <div> 16 | {i18n} 17 | {theme} 18 | {data} 19 | { 20 | // $ExpectError 21 | (i18n: number) 22 | } 23 | { 24 | // $ExpectError 25 | (theme: number) 26 | } 27 | { 28 | // $ExpectError 29 | (data: number) 30 | } 31 | </div> 32 | 33 | const enhancer: HOC<*, EnhancedCompProps> = compose( 34 | fromRenderProps(RenderPropsComponent1, props => ({ 35 | theme: props.theme, 36 | // $ExpectError property not found 37 | err: props.iMNotExists, 38 | })), 39 | fromRenderProps( 40 | RenderPropsComponent2, 41 | props => ({ 42 | i18n: props.i18n, 43 | // $ExpectError property not found 44 | err: props.iMNotExists, 45 | }), 46 | 'render' 47 | ), 48 | fromRenderProps(RenderPropsComponent3, (props, data) => ({ 49 | theme: props.theme, 50 | data: data.data, 51 | // $ExpectError property not found 52 | err: data.iMNotExists, 53 | })) 54 | ) 55 | 56 | const EnhancedComponent = enhancer(Comp) 57 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_functionalEnhancer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react' 4 | import { compose, withProps } from '../..' 5 | import type { HOC } from '../..' 6 | 7 | function mapProps<BaseProps: {}, EnhancedProps>( 8 | mapperFn: EnhancedProps => BaseProps 9 | ): (React.ComponentType<BaseProps>) => React.ComponentType<EnhancedProps> { 10 | return Component => props => <Component {...mapperFn(props)} /> 11 | } 12 | 13 | type EnhancedProps = { hello: string } 14 | 15 | const baseComponent = ({ hello, len }) => 16 | <div> 17 | {(hello: string)} 18 | 19 | { 20 | // $ExpectError 21 | (hello: number) 22 | } 23 | 24 | {(len: number)} 25 | 26 | { 27 | // $ExpectError 28 | (len: string) 29 | } 30 | </div> 31 | 32 | const enhancer: HOC<*, EnhancedProps> = compose( 33 | mapProps(({ hello }) => ({ 34 | hello: `${hello} world`, 35 | len: hello.length, 36 | })), 37 | withProps(props => ({ 38 | helloAndLen: `${props.hello} ${props.len}`, 39 | // $ExpectError 40 | lE: (props.len: string), 41 | })) 42 | ) 43 | 44 | enhancer(baseComponent) 45 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_getContext.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, getContext } from '../..' 5 | // import PropTypes from 'prop-types' 6 | import type { HOC } from '../..' 7 | 8 | const PropTypes = { 9 | number: () => {}, 10 | string: () => {}, 11 | } 12 | 13 | type EnhancedCompProps = { eA: 1 } 14 | 15 | const Comp = ({ eA }) => 16 | <div> 17 | {(eA: number)} 18 | { 19 | // $ExpectError eA nor any nor string 20 | (eA: string) 21 | } 22 | </div> 23 | 24 | const enhacer: HOC<*, EnhancedCompProps> = compose( 25 | getContext({ 26 | // as an idea is to use a hack like this 27 | // so we can test all such types 28 | color: ((PropTypes.string: any): string), 29 | num: ((PropTypes.number: any): number), 30 | }), 31 | withProps(props => ({ 32 | eA: (props.eA: number), 33 | color: (props.color: string), 34 | // $ExpectError eA nor any nor string 35 | eAErr: (props.eA: string), 36 | // $ExpectError color nor any nor number 37 | colorErr: (props.color: number), 38 | })), 39 | withProps(props => ({ 40 | // $ExpectError property not found 41 | err: props.iMNotExists, 42 | })) 43 | ) 44 | 45 | const EnhancedComponent = enhacer(Comp) 46 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_mapProps.js: -------------------------------------------------------------------------------- 1 | /* globals */ 2 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 3 | /* @flow */ 4 | import React from 'react' 5 | import { compose, mapProps, withProps } from '../..' 6 | 7 | import type { HOC } from '../..' 8 | 9 | type EnhancedCompProps = { eA: 1 } 10 | 11 | const Comp = ({ a }) => 12 | <div> 13 | {(a: string)} 14 | { 15 | // $ExpectError 16 | (a: number) 17 | } 18 | </div> 19 | 20 | const enhacer: HOC<*, EnhancedCompProps> = compose( 21 | mapProps(p => ({ 22 | a: '1', 23 | })), 24 | // If you need to to detect erros after a mapProps HOC 25 | // you need to explicitly set Types for all HOCs below 26 | // seems like this https://github.com/facebook/flow/issues/4342 issue 27 | withProps(props => ({ 28 | a: (props.a: string), 29 | // $ExpectError but not 30 | e: Math.round(props.a), 31 | })) 32 | ) 33 | 34 | enhacer(Comp) 35 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_mapPropsStream.js: -------------------------------------------------------------------------------- 1 | /* globals */ 2 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 3 | /* @flow */ 4 | import React from 'react' 5 | // import { Observable } from 'rxjs' 6 | import { compose, mapProps, withProps, mapPropsStream } from '../..' 7 | 8 | import type { HOC } from '../..' 9 | 10 | type EnhancedCompProps = { eA: 1 } 11 | 12 | const Observable = { 13 | of: (a: Object) => Object, 14 | } 15 | 16 | const Comp = ({ a }) => 17 | <div> 18 | {(a: string)} 19 | { 20 | // $ExpectError 21 | (a: number) 22 | } 23 | </div> 24 | 25 | const enhacer: HOC<*, EnhancedCompProps> = compose( 26 | (mapPropsStream((props$: Observable<EnhancedCompProps>) => 27 | Observable.of({ a: 1, b: '1' }) 28 | ): HOC<{ a: string, b: string }, *>), 29 | // If you need to to detect erros after a mapPropsStream HOC (the same for mapProps and some others) 30 | // you need to explicitly set Types for all HOCs below 31 | // but because of this https://github.com/facebook/flow/issues/4342 32 | withProps(props => ({ 33 | a: (props.a: string), 34 | // $ExpectError but not 35 | e: Math.round(props.a), 36 | })) 37 | ) 38 | 39 | enhacer(Comp) 40 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_onlyUpdateForKeys.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, onlyUpdateForKeys } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | onlyUpdateForKeys(['eA']), 21 | withProps(props => ({ 22 | eA: (props.eA: number), 23 | // $ExpectError eA nor any nor string 24 | eAErr: (props.eA: string), 25 | })), 26 | withProps(props => ({ 27 | // $ExpectError property not found 28 | err: props.iMNotExists, 29 | })) 30 | ) 31 | 32 | const enhacerErr: HOC<*, EnhancedCompProps> = compose( 33 | // $ExpectError property not found 34 | onlyUpdateForKeys(['eB']) 35 | ) 36 | 37 | const EnhancedComponent = enhacer(Comp) 38 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_onlyUpdateForPropTypes.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, onlyUpdateForPropTypes } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | onlyUpdateForPropTypes, 21 | withProps(props => ({ 22 | eA: (props.eA: number), 23 | // $ExpectError eA nor any nor string 24 | eAErr: (props.eA: string), 25 | })), 26 | withProps(props => ({ 27 | // $ExpectError property not found 28 | err: props.iMNotExists, 29 | })) 30 | ) 31 | 32 | const EnhancedComponent = enhacer(Comp) 33 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_pure.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, pure } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | pure, 21 | withProps(props => ({ 22 | eA: (props.eA: number), 23 | // $ExpectError eA nor any nor string 24 | eAErr: (props.eA: string), 25 | })), 26 | withProps(props => ({ 27 | // $ExpectError property not found 28 | err: props.iMNotExists, 29 | })) 30 | ) 31 | 32 | const EnhancedComponent = enhacer(Comp) 33 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_shouldUpdate.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, shouldUpdate } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | shouldUpdate((props, nextProps) => { 21 | // $ExpectError eA nor any nor string 22 | ;(props.eA: string) 23 | // $ExpectError eA nor any nor string 24 | ;(nextProps.eA: string) 25 | 26 | return props.eA === nextProps.eA 27 | }), 28 | withProps(props => ({ 29 | eA: (props.eA: number), 30 | // $ExpectError eA nor any nor string 31 | eAErr: (props.eA: string), 32 | })), 33 | withProps(props => ({ 34 | // $ExpectError property not found 35 | err: props.iMNotExists, 36 | })) 37 | ) 38 | 39 | const enhacerErr: HOC<*, EnhancedCompProps> = compose( 40 | shouldUpdate(() => { 41 | // $ExpectError must be boolean 42 | return 1 43 | }) 44 | ) 45 | 46 | const EnhancedComponent = enhacer(Comp) 47 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_statics.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { 5 | compose, 6 | withProps, 7 | setStatic, 8 | setPropTypes, 9 | setDisplayName, 10 | } from '../..' 11 | // import PropTypes from 'prop-types' 12 | import type { HOC } from '../..' 13 | 14 | const PropTypes = { 15 | string: () => {}, 16 | } 17 | 18 | type EnhancedCompProps = { eA: 1 } 19 | 20 | const Comp = ({ eA }) => 21 | <div> 22 | {(eA: number)} 23 | { 24 | // $ExpectError eA nor any nor string 25 | (eA: string) 26 | } 27 | </div> 28 | 29 | const enhacer: HOC<*, EnhancedCompProps> = compose( 30 | setStatic('hello', 'world'), 31 | setPropTypes({ 32 | a: PropTypes.string, 33 | }), 34 | setDisplayName('hello'), 35 | withProps(props => ({ 36 | eA: (props.eA: number), 37 | // $ExpectError eA nor any nor string 38 | eAErr: (props.eA: string), 39 | })), 40 | withProps(props => ({ 41 | // $ExpectError property not found 42 | err: props.iMNotExists, 43 | })) 44 | ) 45 | 46 | // $ExpectError name is string 47 | setDisplayName(1) 48 | 49 | // $ExpectError propTypes is object 50 | setPropTypes(1) 51 | 52 | // $ExpectError name is string 53 | setStatic(1, 'world') 54 | 55 | const EnhancedComponent = enhacer(Comp) 56 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_toClass.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, toClass } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | toClass, 21 | withProps(props => ({ 22 | eA: (props.eA: number), 23 | // $ExpectError eA nor any nor string 24 | eAErr: (props.eA: string), 25 | })), 26 | withProps(props => ({ 27 | // $ExpectError property not found 28 | err: props.iMNotExists, 29 | })) 30 | ) 31 | 32 | const EnhancedComponent = enhacer(Comp) 33 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_toRenderProps.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react' 4 | import { compose, withProps, toRenderProps, withHandlers } from '../..' 5 | import type { HOC } from '../..' 6 | 7 | const enhance: HOC<*, {| +x: number |}> = compose( 8 | withProps(props => ({ 9 | y: props.x + 1, 10 | })), 11 | withHandlers({ 12 | sayHello: ({ y }) => () => { 13 | console.log('Hello', y) 14 | }, 15 | }) 16 | ) 17 | 18 | const WithProps = toRenderProps(enhance) 19 | 20 | const Comp = () => 21 | <WithProps x={1}> 22 | {({ y, sayHello }) => 23 | <div onClick={() => sayHello()}> 24 | {y} 25 | </div>} 26 | </WithProps> 27 | 28 | const Comp2 = () => 29 | // $ExpectError 30 | <WithProps x={'1'}> 31 | {({ y, sayHello }) => 32 | <div onClick={() => sayHello()}> 33 | {y} 34 | </div>} 35 | </WithProps> 36 | 37 | // $ExpectError cannot create `WithProps` element because property `children` is missing in props 38 | const Comp3 = () => <WithProps x={1} /> 39 | 40 | const Comp4 = () => 41 | <WithProps x={1}> 42 | {({ y, sayHello }) => 43 | <div 44 | onClick={() => { 45 | ;(sayHello: () => void) 46 | 47 | // $ExpectError 48 | ;(sayHello: number) 49 | sayHello() 50 | }} 51 | > 52 | {(y: number)} 53 | { 54 | // $ExpectError 55 | (y: string) 56 | } 57 | </div>} 58 | </WithProps> 59 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | 4 | import React from 'react' 5 | import { compose, withProps, hoistStatics } from '../..' 6 | 7 | import type { HOC } from '../..' 8 | 9 | type EnhancedCompProps = { a: number } 10 | 11 | const A = ({ a, b }) => 12 | <div> 13 | {a} 14 | {(b: string)} 15 | { 16 | // $ExpectError 17 | (a: string) 18 | } 19 | { 20 | // $ExpectError 21 | (b: number) 22 | } 23 | </div> 24 | 25 | A.displayName = 'HELLO WORLD' 26 | 27 | const enhacer: HOC<*, EnhancedCompProps> = compose( 28 | withProps(({ a }) => ({ 29 | hello: a, 30 | b: `${a}`, 31 | })) 32 | ) 33 | 34 | hoistStatics(enhacer)(A) 35 | 36 | // I see no reason to test other utils, please add if you think otherwise 37 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_voodoo.js: -------------------------------------------------------------------------------- 1 | /* globals $Exact, $PropertyType */ 2 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 3 | /* @flow */ 4 | import React from 'react' 5 | import { 6 | compose, 7 | withProps, 8 | flattenProp, 9 | renameProp, 10 | renameProps, 11 | withState, 12 | } from '../..' 13 | 14 | import type { HOC } from '../..' 15 | 16 | type EnhancedCompProps = { 17 | eA: number, 18 | obj: { objPropA: string, objPropB: number }, 19 | } 20 | 21 | const Comp = ({ eA, objPropA }) => 22 | <div> 23 | {(eA: number)} 24 | {(objPropA: string)} 25 | { 26 | // $ExpectError eA nor any nor string 27 | (eA: string) 28 | } 29 | { 30 | // $ExpectError eA nor any nor string 31 | (objPropA: number) 32 | } 33 | </div> 34 | 35 | const Comp2 = ({ eA, objPropA }) => 36 | <div> 37 | {/* hack to preview types */} 38 | {/* :: eA, objPropA */} 39 | </div> 40 | 41 | const flattenEnhacer: HOC<*, EnhancedCompProps> = compose( 42 | (flattenProp('obj'): HOC< 43 | { 44 | ...$Exact<EnhancedCompProps>, 45 | ...$Exact<$PropertyType<EnhancedCompProps, 'obj'>>, 46 | }, 47 | EnhancedCompProps 48 | >), 49 | withProps(props => ({ 50 | eA: (props.eA: number), 51 | // $ExpectError 52 | eB: (props.eA: string), 53 | })) 54 | ) 55 | 56 | const EnhancedComponent = flattenEnhacer(Comp) 57 | const EnhancedComponent2 = flattenEnhacer(Comp2) 58 | 59 | // renameEnhacer voodoo (you don't need it, use withProps instead) 60 | const RenameComp = ({ eA, objNew, obj }) => 61 | <div> 62 | {(eA: number)} 63 | 64 | { 65 | // objNew has a type we need 66 | (objNew.objPropA: string) 67 | } 68 | { 69 | // $ExpectError eA nor any nor string 70 | (eA: string) 71 | } 72 | { 73 | // $ExpectError eA nor any nor string 74 | (objNew.objPropA: number) 75 | } 76 | { 77 | // obj is null 78 | (obj: null) 79 | } 80 | { 81 | // $ExpectError eA nor any nor string 82 | (obj: string) 83 | } 84 | </div> 85 | 86 | const renameEnhacer: HOC<*, EnhancedCompProps> = compose( 87 | (renameProp('obj', 'objNew'): HOC< 88 | { 89 | ...$Exact<EnhancedCompProps>, 90 | ...$Exact<{ obj: null }>, 91 | // $PropertyType does not work here 92 | ...$Exact<{ objNew: { objPropA: string, objPropB: number } }>, 93 | }, 94 | EnhancedCompProps 95 | >), 96 | withProps(props => ({ 97 | eA: (props.eA: number), 98 | // $ExpectError 99 | eB: (props.eA: string), 100 | })) 101 | ) 102 | 103 | renameEnhacer(RenameComp) 104 | 105 | const renamePropsEnhacer: HOC<*, EnhancedCompProps> = compose( 106 | (renameProps({ obj: 'objNew' }): HOC< 107 | { 108 | ...$Exact<EnhancedCompProps>, 109 | // --- repeat for every key --- 110 | ...$Exact<{ obj: null }>, 111 | // $PropertyType does not work here 112 | ...$Exact<{ objNew: { objPropA: string, objPropB: number } }>, 113 | }, 114 | EnhancedCompProps 115 | >), 116 | withProps(props => ({ 117 | eA: (props.eA: number), 118 | // $ExpectError 119 | eB: (props.eA: string), 120 | })) 121 | ) 122 | 123 | // use withStateHandlers instead 124 | const withStateEnhancer: HOC<*, EnhancedCompProps> = compose( 125 | (withState('a', 'setA', { hello: 'world' }): HOC< 126 | { 127 | ...$Exact<EnhancedCompProps>, 128 | ...$Exact<{ a: { hello: string }, setA: (a: { hello: string }) => void }>, 129 | }, 130 | EnhancedCompProps 131 | >), 132 | withProps(props => ({ 133 | eA: (props.eA: number), 134 | // $ExpectError 135 | eB: (props.eA: string), 136 | })) 137 | ) 138 | 139 | // withReducer see withState above 140 | // lifecycle see withState above 141 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_withContext.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, withContext } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ eA }) => 11 | <div> 12 | {(eA: number)} 13 | { 14 | // $ExpectError eA nor any nor string 15 | (eA: string) 16 | } 17 | </div> 18 | 19 | const enhacer: HOC<*, EnhancedCompProps> = compose( 20 | withContext({}, props => { 21 | // $ExpectError eA nor any nor string 22 | ;(props.eA: string) 23 | return {} 24 | }), 25 | withProps(props => ({ 26 | eA: (props.eA: number), 27 | // $ExpectError eA nor any nor string 28 | eAErr: (props.eA: string), 29 | })), 30 | withProps(props => ({ 31 | // $ExpectError property not found 32 | err: props.iMNotExists, 33 | })) 34 | ) 35 | 36 | const EnhancedComponent = enhacer(Comp) 37 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_withHandlers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | 4 | import React from 'react' 5 | import { compose, withProps, withHandlers } from '../..' 6 | 7 | import type { HOC } from '../..' 8 | 9 | type EnhancedCompProps = { 10 | value: number, 11 | onChange: (value: number) => void, 12 | onOtherChange: (value: { id: string }) => void, 13 | } 14 | 15 | const enhancer: HOC<*, EnhancedCompProps> = compose( 16 | withHandlers({ 17 | onValueChange: props => value => { 18 | props.onChange(value) 19 | return true 20 | }, 21 | onOtherValueChange: props => value => { 22 | props.onOtherChange(value) 23 | return true 24 | }, 25 | }), 26 | // here props itself will not be infered without explicit handler args types 27 | withProps(props => ({ 28 | valueClone: (props.value: number), 29 | resType: (props.onValueChange(0): boolean), 30 | ee: props.onOtherValueChange({ id: 'aa' }), 31 | 32 | // $ExpectError result is not any or number 33 | resTypeErr: (props.onValueChange(0): number), 34 | // $ExpectError property not found 35 | err: props.iMNotExists, 36 | })) 37 | ) 38 | 39 | // check that factory init works as expected 40 | const enhancer2: HOC<*, EnhancedCompProps> = compose( 41 | withHandlers(() => ({ 42 | onValueChange: props => value => { 43 | props.onChange(value) 44 | return true 45 | }, 46 | })), 47 | // here props itself will not be infered without explicit handler args types 48 | withProps(props => ({ 49 | valueClone: (props.value: number), 50 | resType: (props.onValueChange(0): boolean), 51 | 52 | // $ExpectError result is not any or number 53 | resTypeErr: (props.onValueChange(0): number), 54 | // $ExpectError property not found 55 | err: props.iMNotExists, 56 | })) 57 | ) 58 | 59 | const BaseComp = ({ value, onValueChange }) => 60 | <div 61 | onClick={() => { 62 | const res = onValueChange(1) 63 | ;(res: boolean) 64 | // $ExpectError 65 | ;(res: number) 66 | }} 67 | > 68 | {(value: number)} 69 | { 70 | // $ExpectError value is not any or string 71 | (value: string) 72 | } 73 | </div> 74 | 75 | const Enhanced = enhancer(BaseComp) 76 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_withProps.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { a: string, b: number } 9 | 10 | const Comp = ({ hello, b }) => 11 | <div> 12 | {hello} 13 | {b} 14 | { 15 | // $ExpectError 16 | (b: number) 17 | } 18 | { 19 | // $ExpectError 20 | (hello: number) 21 | } 22 | </div> 23 | 24 | const enhancer: HOC<*, EnhancedCompProps> = compose( 25 | withProps(({ a, b }) => ({ 26 | hello: a, 27 | b: `${b}`, 28 | })), 29 | withProps(({ b, hello }) => ({ 30 | hello: (hello: string), 31 | // $ExpectError (This type is incompatible with number) 32 | c: (b: number), 33 | })), 34 | // check non functional form of with props 35 | withProps({ 36 | d: 'hi', 37 | }), 38 | withProps(props => ({ 39 | a: (props.a: string), 40 | d: (props.d: string), 41 | // $ExpectError property not found 42 | err: props.iMNotExists, 43 | // $ExpectError a not a number and not any 44 | aErr: (props.a: number), 45 | // $ExpectError d not a number and not any 46 | dErr: (props.d: number), 47 | })) 48 | ) 49 | 50 | const EnhancedComponent = enhancer(Comp) 51 | ;<EnhancedComponent a={'1'} b={1} /> 52 | 53 | // $ExpectError 54 | ;<EnhancedComponent a={'1'} b={'1'} /> 55 | 56 | // $ExpectError 57 | ;<EnhancedComponent a={'1'} /> 58 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_withPropsOnChange.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions, arrow-body-style */ 2 | /* @flow */ 3 | import React from 'react' 4 | import { compose, withProps, withPropsOnChange } from '../..' 5 | 6 | import type { HOC } from '../..' 7 | 8 | type EnhancedCompProps = { eA: 1 } 9 | 10 | const Comp = ({ hello, eA }) => 11 | <div> 12 | {(hello: string)} 13 | {(eA: number)} 14 | { 15 | // $ExpectError eA nor any nor string 16 | (eA: string) 17 | } 18 | { 19 | // $ExpectError hello nor any nor number 20 | (hello: number) 21 | } 22 | </div> 23 | 24 | const enhacer: HOC<*, EnhancedCompProps> = compose( 25 | withPropsOnChange(['eA'], ({ eA }) => ({ 26 | hello: `${eA}`, 27 | })), 28 | withProps(props => ({ 29 | hello: (props.hello: string), 30 | eA: (props.eA: number), 31 | // $ExpectError hello nor any nor number 32 | helloErr: (props.hello: number), 33 | // $ExpectError eA nor any nor string 34 | eAErr: (props.eA: string), 35 | })), 36 | withProps(props => ({ 37 | // $ExpectError property not found 38 | err: props.iMNotExists, 39 | })) 40 | ) 41 | 42 | const enhacerFn: HOC<*, EnhancedCompProps> = compose( 43 | withPropsOnChange( 44 | (props, nextProps) => { 45 | ;(props.eA: number) 46 | ;(nextProps.eA: number) 47 | // $ExpectError eA nor any nor string 48 | ;(props.eA: string) 49 | // $ExpectError eA nor any nor string 50 | ;(nextProps.eA: string) 51 | return props.eA === props.eA 52 | }, 53 | ({ eA }) => ({ 54 | hello: `${eA}`, 55 | }) 56 | ), 57 | withProps(props => ({ 58 | hello: (props.hello: string), 59 | eA: (props.eA: number), 60 | // $ExpectError hello nor any nor number 61 | helloErr: (props.hello: number), 62 | // $ExpectError eA nor any nor string 63 | eAErr: (props.eA: string), 64 | })) 65 | ) 66 | 67 | const enhacerErr: HOC<*, EnhancedCompProps> = compose( 68 | // $ExpectError property property `eB` not found 69 | withPropsOnChange(['eA', 'eB'], ({ eA }) => ({ 70 | hello: `${eA}`, 71 | })) 72 | ) 73 | 74 | const enhacerFnErr: HOC<*, EnhancedCompProps> = compose( 75 | withPropsOnChange( 76 | (props, nextProps) => { 77 | // $ExpectError boolean 78 | return 1 79 | }, 80 | ({ eA }) => ({ 81 | hello: `${eA}`, 82 | }) 83 | ), 84 | withProps(props => ({ 85 | hello: (props.hello: string), 86 | eA: (props.eA: number), 87 | })) 88 | ) 89 | 90 | const EnhancedComponent = enhacer(Comp) 91 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/types/test_withStateHandlers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars, no-unused-expressions */ 2 | /* @flow */ 3 | 4 | import React from 'react' 5 | import { compose, withProps, withStateHandlers } from '../..' 6 | 7 | import type { HOC } from '../..' 8 | 9 | type EnhancedCompProps = { 10 | initialCounter: number, 11 | } 12 | 13 | const enhancer: HOC<*, EnhancedCompProps> = compose( 14 | withStateHandlers( 15 | { value: 'Hello', letIt: 'be', obj: ({}: { [key: string]: string }) }, 16 | { 17 | // we need to set argument type so inference will work good 18 | setValue: (state, props) => (value: string) => ({ 19 | value, 20 | }), 21 | changeValue: (state, props) => ( 22 | { i, j }: { i: number, j: string }, 23 | k: number 24 | ) => ({ 25 | value: `world again ${i} ${j}`, 26 | }), 27 | inform: state => () => {}, 28 | } 29 | ), 30 | // here props itself will not be infered without explicit handler args types 31 | withProps(props => ({ 32 | hi: (props.value: string), 33 | ic: (props.initialCounter: number), 34 | cc: (props.obj.a: string), 35 | // $ExpectError value not a number or any 36 | ehi: (props.value: number), 37 | // $ExpectError not a number 38 | cn: (props.obj.a: number), 39 | // $ExpectError property not found (to detect that props is not any) 40 | err: props.iMNotExists, 41 | // $ExpectError initialCounter not any nor string 42 | icErr: (props.initialCounter: string), 43 | })) 44 | ) 45 | 46 | const enhancerFuncInit: HOC<*, EnhancedCompProps> = compose( 47 | withStateHandlers( 48 | props => ({ 49 | counter: props.initialCounter, 50 | }), 51 | { 52 | // it's better to set argument type with named props, easier to find an error 53 | // if you call it with wrong arguments 54 | incCounter: ({ counter }) => ({ value }: { value: number }) => ({ 55 | counter: counter + value, 56 | }), 57 | } 58 | ), 59 | withProps(props => ({ 60 | // check that result is void 61 | iVal: (props.incCounter({ value: 1 }): void), 62 | // $ExpectError check that incCounter is not any 63 | iVal2: (props.incCounter({ value: 1 }): number), 64 | // $ExpectError property not found 65 | err: props.iMNotExists, 66 | })) 67 | ) 68 | 69 | const BaseComponent = ({ hi, changeValue, setValue }) => 70 | <div 71 | onClick={() => { 72 | // check that supports few arguments 73 | const x = changeValue({ i: 1, j: '1' }, 1) 74 | 75 | setValue('ww') 76 | // Check that result is void 77 | ;(x: void) 78 | 79 | // $ExpectError check that x is not any 80 | ;(x: {}) 81 | 82 | // Check hi 83 | ;(hi: string) 84 | 85 | // $ExpectError check that hi is not any 86 | ;(hi: number) 87 | }} 88 | > 89 | {hi} 90 | </div> 91 | 92 | const EnhancedComponent = enhancer(BaseComponent) 93 | ;<EnhancedComponent initialCounter={0} /> 94 | 95 | // Without $Exact<State> this will cause error 96 | const enhancer3: HOC<*, EnhancedCompProps> = compose( 97 | withStateHandlers( 98 | ({ 99 | mapA2B: {}, 100 | }: { mapA2B: { [key: string]: string } }), 101 | {} 102 | ), 103 | withProps(props => ({ 104 | // check that result is void 105 | iVal: props.mapA2B.c, 106 | })) 107 | ) 108 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import setDisplayName from '../setDisplayName' 3 | import wrapDisplayName from '../wrapDisplayName' 4 | 5 | export const countRenders = BaseComponent => { 6 | class CountRenders extends React.Component { 7 | renderCount = 0 8 | 9 | render() { 10 | this.renderCount += 1 11 | return <BaseComponent renderCount={this.renderCount} {...this.props} /> 12 | } 13 | } 14 | 15 | return setDisplayName(wrapDisplayName(BaseComponent, 'countRenders'))( 16 | CountRenders 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withContext-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/require-default-props */ 2 | import React, { Component } from 'react' 3 | import PropTypes from 'prop-types' 4 | import { mount } from 'enzyme' 5 | import sinon from 'sinon' 6 | import { withContext, getContext, compose, mapProps } from '../' 7 | 8 | test('withContext + getContext adds to and grabs from context', () => { 9 | // Mini React Redux clone 10 | const store = { 11 | getState: () => ({ 12 | todos: ['eat', 'drink', 'sleep'], 13 | counter: 12, 14 | }), 15 | } 16 | 17 | class BaseProvider extends Component { 18 | static propTypes = { 19 | children: PropTypes.node, 20 | } 21 | 22 | render() { 23 | return this.props.children 24 | } 25 | } 26 | 27 | const Provider = compose( 28 | withContext({ store: PropTypes.object }, props => ({ store: props.store })) 29 | )(BaseProvider) 30 | 31 | expect(Provider.displayName).toBe('withContext(BaseProvider)') 32 | 33 | const connect = selector => 34 | compose( 35 | getContext({ store: PropTypes.object }), 36 | mapProps(props => selector(props.store.getState())) 37 | ) 38 | 39 | const component = sinon.spy(() => null) 40 | component.displayName = 'component' 41 | 42 | const TodoList = connect(({ todos }) => ({ todos }))(component) 43 | 44 | expect(TodoList.displayName).toBe('getContext(mapProps(component))') 45 | 46 | mount( 47 | <Provider store={store}> 48 | <TodoList /> 49 | </Provider> 50 | ) 51 | 52 | expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink', 'sleep']) 53 | }) 54 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withHandlers-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { withHandlers, withState, compose } from '../' 5 | 6 | test('withHandlers passes handlers to base component', () => { 7 | let submittedFormValue 8 | const enhanceForm = compose( 9 | withState('value', 'updateValue', ''), 10 | withHandlers({ 11 | onChange: props => event => { 12 | props.updateValue(event.target.value) 13 | }, 14 | onSubmit: props => () => { 15 | submittedFormValue = props.value 16 | }, 17 | }) 18 | ) 19 | 20 | const Form = enhanceForm(({ value, onChange, onSubmit }) => 21 | <form onSubmit={onSubmit}> 22 | <label> 23 | Value 24 | <input type="text" value={value} onChange={onChange} /> 25 | </label> 26 | <p> 27 | {value} 28 | </p> 29 | </form> 30 | ) 31 | 32 | const wrapper = mount(<Form />) 33 | const input = wrapper.find('input') 34 | const output = wrapper.find('p') 35 | const form = wrapper.find('form') 36 | 37 | input.simulate('change', { target: { value: 'Yay' } }) 38 | expect(output.text()).toBe('Yay') 39 | 40 | input.simulate('change', { target: { value: 'Yay!!' } }) 41 | expect(output.text()).toBe('Yay!!') 42 | 43 | form.simulate('submit') 44 | expect(submittedFormValue).toBe('Yay!!') 45 | }) 46 | 47 | test('withHandlers passes immutable handlers', () => { 48 | const enhance = withHandlers({ 49 | handler: () => () => null, 50 | }) 51 | const component = sinon.spy(() => null) 52 | const Div = enhance(component) 53 | 54 | const wrapper = mount(<Div />) 55 | wrapper.setProps({ foo: 'bar' }) 56 | 57 | expect(component.calledTwice).toBe(true) 58 | expect(component.firstCall.args[0].handler).toBe( 59 | component.secondCall.args[0].handler 60 | ) 61 | }) 62 | 63 | test('withHandlers warns if handler is not a higher-order function', () => { 64 | const error = sinon.stub(console, 'error') 65 | 66 | const Button = withHandlers({ 67 | onClick: () => {}, 68 | })('button') 69 | 70 | const wrapper = mount(<Button />) 71 | const button = wrapper.find('button') 72 | 73 | expect(() => button.simulate('click')).toThrowError(/undefined/) 74 | 75 | expect(error.firstCall.args[0]).toBe( 76 | 'withHandlers(): Expected a map of higher-order functions. Refer to ' + 77 | 'the docs for more info.' 78 | ) 79 | 80 | /* eslint-disable */ 81 | console.error.restore() 82 | /* eslint-enable */ 83 | }) 84 | 85 | test('withHandlers allow handers to be a factory', () => { 86 | const enhance = withHandlers(initialProps => { 87 | let cache_ 88 | 89 | return { 90 | handler: () => () => { 91 | if (cache_) { 92 | return cache_ 93 | } 94 | cache_ = { ...initialProps } 95 | 96 | return cache_ 97 | }, 98 | } 99 | }) 100 | 101 | const componentHandlers = [] 102 | const componentHandlers2 = [] 103 | 104 | const Component = enhance(({ handler }) => { 105 | componentHandlers.push(handler()) 106 | return null 107 | }) 108 | 109 | const Component2 = enhance(({ handler }) => { 110 | componentHandlers2.push(handler()) 111 | return null 112 | }) 113 | 114 | const wrapper = mount(<Component hello={'foo'} />) 115 | wrapper.setProps({ hello: 'bar' }) 116 | expect(componentHandlers[0]).toBe(componentHandlers[1]) 117 | 118 | // check that cache is not shared 119 | mount(<Component2 hello={'foo'} />) 120 | expect(componentHandlers[0]).toEqual(componentHandlers2[0]) 121 | expect(componentHandlers[0]).not.toBe(componentHandlers2[0]) 122 | }) 123 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withProps-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { withProps } from '../' 4 | 5 | test('withProps passes additional props to base component', () => { 6 | const DoReMi = withProps({ 'data-so': 'do', 'data-la': 'fa' })('div') 7 | expect(DoReMi.displayName).toBe('withProps(div)') 8 | 9 | const div = shallow(<DoReMi />).find('div') 10 | expect(div.prop('data-so')).toBe('do') 11 | expect(div.prop('data-la')).toBe('fa') 12 | }) 13 | 14 | test('withProps takes precedent over owner props', () => { 15 | const DoReMi = withProps({ 'data-so': 'do', 'data-la': 'fa' })('div') 16 | 17 | const div = shallow(<DoReMi data-la="ti" />).find('div') 18 | expect(div.prop('data-so')).toBe('do') 19 | expect(div.prop('data-la')).toBe('fa') 20 | }) 21 | 22 | test('withProps should accept function', () => { 23 | const DoReMi = withProps(props => ({ 24 | 'data-so': props['data-la'], 25 | }))('div') 26 | 27 | const div = shallow(<DoReMi data-la="la" />).find('div') 28 | expect(div.prop('data-so')).toBe('la') 29 | }) 30 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withPropsOnChange-test.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { 5 | withPropsOnChange, 6 | withState, 7 | withStateHandlers, 8 | flattenProp, 9 | compose, 10 | } from '../' 11 | 12 | test('withPropsOnChange maps subset of owner props to child props', () => { 13 | const component = sinon.spy(() => null) 14 | component.displayName = 'component' 15 | 16 | const mapSpy = sinon.spy() 17 | const StringConcat = compose( 18 | withState('strings', 'updateStrings', { a: 'a', b: 'b', c: 'c' }), 19 | flattenProp('strings'), 20 | withPropsOnChange(['a', 'b'], ({ a, b, ...props }) => { 21 | mapSpy() 22 | return { 23 | ...props, 24 | foobar: a + b, 25 | } 26 | }) 27 | )(component) 28 | 29 | expect(StringConcat.displayName).toBe( 30 | 'withState(flattenProp(withPropsOnChange(component)))' 31 | ) 32 | 33 | mount(<StringConcat />) 34 | const { updateStrings } = component.firstCall.args[0] 35 | expect(component.lastCall.args[0].foobar).toBe('ab') 36 | expect(component.calledOnce).toBe(true) 37 | expect(mapSpy.callCount).toBe(1) 38 | 39 | // Does not re-map for non-dependent prop updates 40 | updateStrings(strings => ({ ...strings, c: 'baz' })) 41 | expect(component.lastCall.args[0].foobar).toBe('ab') 42 | expect(component.lastCall.args[0].c).toBe('c') 43 | expect(component.calledTwice).toBe(true) 44 | expect(mapSpy.callCount).toBe(1) 45 | 46 | updateStrings(strings => ({ ...strings, a: 'foo', b: 'bar' })) 47 | expect(component.lastCall.args[0].foobar).toBe('foobar') 48 | expect(component.lastCall.args[0].c).toBe('baz') 49 | expect(component.calledThrice).toBe(true) 50 | expect(mapSpy.callCount).toBe(2) 51 | }) 52 | 53 | test('withPropsOnChange maps subset of owner props to child props with custom predicate', () => { 54 | const component = sinon.spy(() => null) 55 | component.displayName = 'component' 56 | 57 | const mapSpy = sinon.spy() 58 | const shouldMapSpy = sinon.spy() 59 | const PageContainer = compose( 60 | withStateHandlers( 61 | { result: { hasError: false, loading: true, error: null } }, 62 | { 63 | updateResult: ({ result }) => payload => ({ 64 | result: { ...result, ...payload }, 65 | }), 66 | } 67 | ), 68 | withPropsOnChange( 69 | ({ result }, { result: nextResult }) => { 70 | shouldMapSpy(result, nextResult) 71 | return !result.hasError && nextResult.hasError 72 | }, 73 | ({ result: { hasError, error } }) => { 74 | mapSpy() 75 | 76 | if (hasError) { 77 | return { 78 | errorEverHappened: true, 79 | lastError: error, 80 | } 81 | } 82 | 83 | return { 84 | errorEverHappened: false, 85 | } 86 | } 87 | ) 88 | )(component) 89 | 90 | expect(PageContainer.displayName).toBe( 91 | 'withStateHandlers(withPropsOnChange(component))' 92 | ) 93 | 94 | mount(<PageContainer />) 95 | const { updateResult } = component.firstCall.args[0] 96 | expect(component.lastCall.args[0].errorEverHappened).toBe(false) 97 | expect(component.lastCall.args[0].lastError).toBeUndefined() 98 | expect(component.calledOnce).toBe(true) 99 | expect(mapSpy.callCount).toBe(1) 100 | expect(shouldMapSpy.callCount).toBe(1) 101 | 102 | updateResult({ loading: false, hasError: true, error: '1' }) 103 | expect(component.lastCall.args[0].errorEverHappened).toBe(true) 104 | expect(component.lastCall.args[0].lastError).toBe('1') 105 | expect(component.calledTwice).toBe(true) 106 | expect(mapSpy.callCount).toBe(2) 107 | 108 | // Does not re-map for false map result 109 | updateResult({ loading: true, hasError: false, error: null }) 110 | expect(component.lastCall.args[0].errorEverHappened).toBe(true) 111 | expect(component.lastCall.args[0].lastError).toBe('1') 112 | expect(component.calledThrice).toBe(true) 113 | expect(mapSpy.callCount).toBe(2) 114 | 115 | updateResult({ loading: false, hasError: true, error: '2' }) 116 | expect(component.lastCall.args[0].errorEverHappened).toBe(true) 117 | expect(component.lastCall.args[0].lastError).toBe('2') 118 | expect(component.callCount).toBe(4) 119 | expect(mapSpy.callCount).toBe(3) 120 | }) 121 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withReducer-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | import { withReducer, compose, flattenProp } from '../' 5 | 6 | const SET_COUNTER = 'SET_COUNTER' 7 | 8 | test('adds a stateful value and a function for updating it', () => { 9 | const component = sinon.spy(() => null) 10 | component.displayName = 'component' 11 | 12 | const initialState = { counter: 0 } 13 | 14 | const reducer = (state, action) => 15 | action.type === SET_COUNTER ? { counter: action.payload } : state 16 | 17 | const Counter = compose( 18 | withReducer('state', 'dispatch', reducer, initialState), 19 | flattenProp('state') 20 | )(component) 21 | 22 | expect(Counter.displayName).toBe('withReducer(flattenProp(component))') 23 | 24 | mount(<Counter />) 25 | const { dispatch } = component.firstCall.args[0] 26 | 27 | expect(component.lastCall.args[0].counter).toBe(0) 28 | 29 | dispatch({ type: SET_COUNTER, payload: 18 }) 30 | expect(component.lastCall.args[0].counter).toBe(18) 31 | }) 32 | 33 | test('calls initialState when it is a function', () => { 34 | const component = sinon.spy(() => null) 35 | component.displayName = 'component' 36 | 37 | const initialState = ({ initialCount }) => ({ counter: initialCount }) 38 | 39 | const reducer = (state, action) => 40 | action.type === SET_COUNTER ? { counter: action.payload } : state 41 | 42 | const Counter = compose( 43 | withReducer('state', 'dispatch', reducer, initialState), 44 | flattenProp('state') 45 | )(component) 46 | 47 | mount(<Counter initialCount={10} />) 48 | 49 | expect(component.lastCall.args[0].counter).toBe(10) 50 | }) 51 | 52 | test('receives state from reducer when initialState is not provided', () => { 53 | const component = sinon.spy(() => null) 54 | component.displayName = 'component' 55 | 56 | const initialState = { counter: 0 } 57 | 58 | const reducer = (state = initialState, action) => 59 | action.type === SET_COUNTER ? { counter: action.payload } : state 60 | 61 | const Counter = compose( 62 | withReducer('state', 'dispatch', reducer), 63 | flattenProp('state') 64 | )(component) 65 | 66 | mount(<Counter />) 67 | 68 | expect(component.lastCall.args[0].counter).toBe(0) 69 | }) 70 | 71 | test('calls the given callback with new state after a dispatch call', () => { 72 | const component = sinon.spy(() => null) 73 | component.displayName = 'component' 74 | 75 | const initialState = { counter: 0 } 76 | 77 | const reducer = (state, action) => 78 | action.type === SET_COUNTER ? { counter: action.payload } : state 79 | 80 | const Counter = compose( 81 | withReducer('state', 'dispatch', reducer, initialState), 82 | flattenProp('state') 83 | )(component) 84 | 85 | mount(<Counter />) 86 | const dispatch = sinon.spy(component.firstCall.args[0].dispatch) 87 | const callback = sinon.spy() 88 | 89 | dispatch({ type: SET_COUNTER, payload: 11 }, callback) 90 | expect(dispatch.calledBefore(callback)).toBe(true) 91 | expect(dispatch.calledOnce).toBe(true) 92 | expect(callback.calledAfter(dispatch)).toBe(true) 93 | expect(callback.calledOnce).toBe(true) 94 | expect(callback.getCall(0).args.length).toBe(1) 95 | expect(callback.getCall(0).args[0]).toEqual({ counter: 11 }) 96 | }) 97 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withState-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | 5 | import { withState } from '../' 6 | 7 | test('withState adds a stateful value and a function for updating it', () => { 8 | const component = sinon.spy(() => null) 9 | component.displayName = 'component' 10 | 11 | const Counter = withState('counter', 'updateCounter', 0)(component) 12 | expect(Counter.displayName).toBe('withState(component)') 13 | 14 | mount(<Counter pass="through" />) 15 | const { updateCounter } = component.firstCall.args[0] 16 | 17 | expect(component.lastCall.args[0].counter).toBe(0) 18 | expect(component.lastCall.args[0].pass).toBe('through') 19 | 20 | updateCounter(n => n + 9) 21 | updateCounter(n => n * 2) 22 | 23 | expect(component.lastCall.args[0].counter).toBe(18) 24 | expect(component.lastCall.args[0].pass).toBe('through') 25 | }) 26 | 27 | test('withState also accepts a non-function, which is passed directly to setState()', () => { 28 | const component = sinon.spy(() => null) 29 | component.displayName = 'component' 30 | 31 | const Counter = withState('counter', 'updateCounter', 0)(component) 32 | mount(<Counter />) 33 | const { updateCounter } = component.firstCall.args[0] 34 | 35 | updateCounter(18) 36 | expect(component.lastCall.args[0].counter).toBe(18) 37 | }) 38 | 39 | test('withState accepts setState() callback', () => { 40 | const component = sinon.spy(() => null) 41 | component.displayName = 'component' 42 | 43 | const Counter = withState('counter', 'updateCounter', 0)(component) 44 | mount(<Counter />) 45 | const { updateCounter } = component.firstCall.args[0] 46 | 47 | const renderSpy = sinon.spy(() => { 48 | expect(component.lastCall.args[0].counter).toBe(18) 49 | }) 50 | 51 | expect(component.lastCall.args[0].counter).toBe(0) 52 | updateCounter(18, renderSpy) 53 | }) 54 | 55 | test('withState also accepts initialState as function of props', () => { 56 | const component = sinon.spy(() => null) 57 | component.displayName = 'component' 58 | 59 | const Counter = withState( 60 | 'counter', 61 | 'updateCounter', 62 | props => props.initialCounter 63 | )(component) 64 | 65 | mount(<Counter initialCounter={1} />) 66 | const { updateCounter } = component.firstCall.args[0] 67 | 68 | expect(component.lastCall.args[0].counter).toBe(1) 69 | updateCounter(n => n * 3) 70 | expect(component.lastCall.args[0].counter).toBe(3) 71 | }) 72 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/withStateHandlers-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import sinon from 'sinon' 4 | 5 | import { compose, withStateHandlers } from '../' 6 | 7 | test('withStateHandlers should persist events passed as argument', () => { 8 | const component = ({ value, onChange }) => 9 | <div> 10 | <input type="text" value={value} onChange={onChange} /> 11 | <p> 12 | {value} 13 | </p> 14 | </div> 15 | 16 | const InputComponent = withStateHandlers( 17 | { value: '' }, 18 | { 19 | onChange: () => e => ({ 20 | value: e.target.value, 21 | }), 22 | } 23 | )(component) 24 | 25 | const wrapper = mount(<InputComponent />) 26 | const input = wrapper.find('input') 27 | const output = wrapper.find('p') 28 | // having that enzyme simulate does not simulate real situation 29 | // emulate persist 30 | input.simulate('change', { 31 | persist() { 32 | this.target = { value: 'Yay' } 33 | }, 34 | }) 35 | expect(output.text()).toBe('Yay') 36 | 37 | input.simulate('change', { target: { value: 'empty' } }) 38 | expect(output.text()).toBe('empty') 39 | }) 40 | 41 | test('withStateHandlers adds a stateful value and a function for updating it', () => { 42 | const component = sinon.spy(() => null) 43 | component.displayName = 'component' 44 | 45 | const Counter = withStateHandlers( 46 | { counter: 0 }, 47 | { 48 | updateCounter: ({ counter }) => increment => ({ 49 | counter: counter + increment, 50 | }), 51 | } 52 | )(component) 53 | expect(Counter.displayName).toBe('withStateHandlers(component)') 54 | 55 | mount(<Counter pass="through" />) 56 | const { updateCounter } = component.firstCall.args[0] 57 | 58 | expect(component.lastCall.args[0].counter).toBe(0) 59 | expect(component.lastCall.args[0].pass).toBe('through') 60 | 61 | updateCounter(9) 62 | expect(component.lastCall.args[0].counter).toBe(9) 63 | updateCounter(1) 64 | updateCounter(10) 65 | 66 | expect(component.lastCall.args[0].counter).toBe(20) 67 | expect(component.lastCall.args[0].pass).toBe('through') 68 | }) 69 | 70 | test('withStateHandlers accepts initialState as function of props', () => { 71 | const component = sinon.spy(() => null) 72 | component.displayName = 'component' 73 | 74 | const Counter = withStateHandlers( 75 | ({ initialCounter }) => ({ 76 | counter: initialCounter, 77 | }), 78 | { 79 | updateCounter: ({ counter }) => increment => ({ 80 | counter: counter + increment, 81 | }), 82 | } 83 | )(component) 84 | 85 | const initialCounter = 101 86 | 87 | mount(<Counter initialCounter={initialCounter} />) 88 | expect(component.lastCall.args[0].counter).toBe(initialCounter) 89 | }) 90 | 91 | test('withStateHandlers initial state must be function or object or null or undefined', () => { 92 | const component = sinon.spy(() => null) 93 | component.displayName = 'component' 94 | 95 | const Counter = withStateHandlers(1, {})(component) 96 | // React throws an error 97 | // expect(() => mount(<Counter />)).toThrow() 98 | const error = sinon.stub(console, 'error') 99 | mount(<Counter />) 100 | expect(error.called).toBe(true) 101 | }) 102 | 103 | test('withStateHandlers have access to props', () => { 104 | const component = sinon.spy(() => null) 105 | component.displayName = 'component' 106 | 107 | const Counter = withStateHandlers( 108 | ({ initialCounter }) => ({ 109 | counter: initialCounter, 110 | }), 111 | { 112 | increment: ({ counter }, { incrementValue }) => () => ({ 113 | counter: counter + incrementValue, 114 | }), 115 | } 116 | )(component) 117 | 118 | const initialCounter = 101 119 | const incrementValue = 37 120 | 121 | mount( 122 | <Counter initialCounter={initialCounter} incrementValue={incrementValue} /> 123 | ) 124 | 125 | const { increment } = component.firstCall.args[0] 126 | 127 | increment() 128 | expect(component.lastCall.args[0].counter).toBe( 129 | initialCounter + incrementValue 130 | ) 131 | }) 132 | 133 | test('withStateHandlers passes immutable state updaters', () => { 134 | const component = sinon.spy(() => null) 135 | component.displayName = 'component' 136 | 137 | const Counter = withStateHandlers( 138 | ({ initialCounter }) => ({ 139 | counter: initialCounter, 140 | }), 141 | { 142 | increment: ({ counter }, { incrementValue }) => () => ({ 143 | counter: counter + incrementValue, 144 | }), 145 | } 146 | )(component) 147 | 148 | const initialCounter = 101 149 | const incrementValue = 37 150 | 151 | mount( 152 | <Counter initialCounter={initialCounter} incrementValue={incrementValue} /> 153 | ) 154 | 155 | const { increment } = component.firstCall.args[0] 156 | 157 | increment() 158 | expect(component.lastCall.args[0].counter).toBe( 159 | initialCounter + incrementValue 160 | ) 161 | }) 162 | 163 | test('withStateHandlers does not rerender if state updater returns undefined', () => { 164 | const component = sinon.spy(() => null) 165 | component.displayName = 'component' 166 | 167 | const Counter = withStateHandlers( 168 | ({ initialCounter }) => ({ 169 | counter: initialCounter, 170 | }), 171 | { 172 | updateCounter: ({ counter }) => increment => 173 | increment === 0 174 | ? undefined 175 | : { 176 | counter: counter + increment, 177 | }, 178 | } 179 | )(component) 180 | 181 | const initialCounter = 101 182 | 183 | mount(<Counter initialCounter={initialCounter} />) 184 | expect(component.callCount).toBe(1) 185 | 186 | const { updateCounter } = component.firstCall.args[0] 187 | 188 | updateCounter(1) 189 | expect(component.callCount).toBe(2) 190 | 191 | updateCounter(0) 192 | expect(component.callCount).toBe(2) 193 | }) 194 | 195 | test('withStateHandlers rerenders if parent props changed', () => { 196 | const component = sinon.spy(() => null) 197 | component.displayName = 'component' 198 | 199 | const Counter = compose( 200 | withStateHandlers( 201 | ({ initialCounter }) => ({ 202 | counter: initialCounter, 203 | }), 204 | { 205 | increment: ({ counter }) => incrementValue => ({ 206 | counter: counter + incrementValue, 207 | }), 208 | } 209 | ), 210 | withStateHandlers( 211 | { incrementValue: 1 }, 212 | { 213 | // updates parent state and return undefined 214 | updateParentIncrement: ({ incrementValue }, { increment }) => () => { 215 | increment(incrementValue) 216 | return undefined 217 | }, 218 | } 219 | ) 220 | )(component) 221 | 222 | const initialCounter = 101 223 | 224 | mount(<Counter initialCounter={initialCounter} />) 225 | 226 | const { updateParentIncrement } = component.firstCall.args[0] 227 | 228 | updateParentIncrement() 229 | expect(component.lastCall.args[0].counter).toBe(initialCounter + 1) 230 | }) 231 | -------------------------------------------------------------------------------- /src/packages/recompose/__tests__/wrapDisplayName-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { wrapDisplayName } from '../' 3 | 4 | test('wrapDisplayName wraps the display name of a React component with the name of an HoC, Relay-style', () => { 5 | class SomeComponent extends React.Component { 6 | render() { 7 | return <div /> 8 | } 9 | } 10 | 11 | expect(wrapDisplayName(SomeComponent, 'someHoC')).toBe( 12 | 'someHoC(SomeComponent)' 13 | ) 14 | }) 15 | -------------------------------------------------------------------------------- /src/packages/recompose/baconObservableConfig.js: -------------------------------------------------------------------------------- 1 | import $observable from 'symbol-observable' 2 | import Bacon from 'baconjs' 3 | 4 | const config = { 5 | fromESObservable: observable => 6 | Bacon.fromBinder(sink => { 7 | const { unsubscribe } = observable.subscribe({ 8 | next: val => sink(new Bacon.Next(val)), 9 | error: err => sink(new Bacon.Error(err)), 10 | complete: () => sink(new Bacon.End()), 11 | }) 12 | return unsubscribe 13 | }), 14 | toESObservable: stream => ({ 15 | subscribe: observer => { 16 | const unsubscribe = stream.subscribe(event => { 17 | if (event.hasValue()) { 18 | observer.next(event.value()) 19 | } else if (event.isError()) { 20 | observer.error(event.error) 21 | } else if (event.isEnd()) { 22 | observer.complete() 23 | } 24 | }) 25 | return { unsubscribe } 26 | }, 27 | [$observable]() { 28 | return this 29 | }, 30 | }), 31 | } 32 | 33 | export default config 34 | -------------------------------------------------------------------------------- /src/packages/recompose/branch.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const identity = Component => Component 6 | 7 | const branch = (test, left, right = identity) => BaseComponent => { 8 | let leftFactory 9 | let rightFactory 10 | const Branch = props => { 11 | if (test(props)) { 12 | leftFactory = leftFactory || createFactory(left(BaseComponent)) 13 | return leftFactory(props) 14 | } 15 | rightFactory = rightFactory || createFactory(right(BaseComponent)) 16 | return rightFactory(props) 17 | } 18 | 19 | if (process.env.NODE_ENV !== 'production') { 20 | return setDisplayName(wrapDisplayName(BaseComponent, 'branch'))(Branch) 21 | } 22 | return Branch 23 | } 24 | 25 | export default branch 26 | -------------------------------------------------------------------------------- /src/packages/recompose/componentFromProp.js: -------------------------------------------------------------------------------- 1 | import { createElement } from 'react' 2 | import omit from './utils/omit' 3 | 4 | const componentFromProp = propName => { 5 | const Component = props => 6 | createElement(props[propName], omit(props, [propName])) 7 | Component.displayName = `componentFromProp(${propName})` 8 | return Component 9 | } 10 | 11 | export default componentFromProp 12 | -------------------------------------------------------------------------------- /src/packages/recompose/componentFromStream.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import { createChangeEmitter } from 'change-emitter' 3 | import $observable from 'symbol-observable' 4 | import { config as globalConfig } from './setObservableConfig' 5 | 6 | export const componentFromStreamWithConfig = config => propsToVdom => 7 | class ComponentFromStream extends Component { 8 | state = { vdom: null } 9 | 10 | propsEmitter = createChangeEmitter() 11 | 12 | // Stream of props 13 | props$ = config.fromESObservable({ 14 | subscribe: observer => { 15 | const unsubscribe = this.propsEmitter.listen(props => { 16 | if (props) { 17 | observer.next(props) 18 | } else { 19 | observer.complete() 20 | } 21 | }) 22 | return { unsubscribe } 23 | }, 24 | [$observable]() { 25 | return this 26 | }, 27 | }) 28 | 29 | // Stream of vdom 30 | vdom$ = config.toESObservable(propsToVdom(this.props$)) 31 | 32 | componentWillMount() { 33 | // Subscribe to child prop changes so we know when to re-render 34 | this.subscription = this.vdom$.subscribe({ 35 | next: vdom => { 36 | this.setState({ vdom }) 37 | }, 38 | }) 39 | this.propsEmitter.emit(this.props) 40 | } 41 | 42 | componentWillReceiveProps(nextProps) { 43 | // Receive new props from the owner 44 | this.propsEmitter.emit(nextProps) 45 | } 46 | 47 | shouldComponentUpdate(nextProps, nextState) { 48 | return nextState.vdom !== this.state.vdom 49 | } 50 | 51 | componentWillUnmount() { 52 | // Call without arguments to complete stream 53 | this.propsEmitter.emit() 54 | 55 | // Clean-up subscription before un-mounting 56 | this.subscription.unsubscribe() 57 | } 58 | 59 | render() { 60 | return this.state.vdom 61 | } 62 | } 63 | 64 | const componentFromStream = propsToVdom => 65 | componentFromStreamWithConfig(globalConfig)(propsToVdom) 66 | 67 | export default componentFromStream 68 | -------------------------------------------------------------------------------- /src/packages/recompose/compose.js: -------------------------------------------------------------------------------- 1 | const compose = (...funcs) => 2 | funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg) 3 | 4 | export default compose 5 | -------------------------------------------------------------------------------- /src/packages/recompose/createEventHandler.js: -------------------------------------------------------------------------------- 1 | import $observable from 'symbol-observable' 2 | import { createChangeEmitter } from 'change-emitter' 3 | import { config as globalConfig } from './setObservableConfig' 4 | 5 | export const createEventHandlerWithConfig = config => () => { 6 | const emitter = createChangeEmitter() 7 | const stream = config.fromESObservable({ 8 | subscribe(observer) { 9 | const unsubscribe = emitter.listen(value => observer.next(value)) 10 | return { unsubscribe } 11 | }, 12 | [$observable]() { 13 | return this 14 | }, 15 | }) 16 | return { 17 | handler: emitter.emit, 18 | stream, 19 | } 20 | } 21 | 22 | const createEventHandler = createEventHandlerWithConfig(globalConfig) 23 | 24 | export default createEventHandler 25 | -------------------------------------------------------------------------------- /src/packages/recompose/createSink.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import { polyfill } from 'react-lifecycles-compat' 3 | 4 | const createSink = callback => { 5 | class Sink extends Component { 6 | state = {} 7 | 8 | static getDerivedStateFromProps(nextProps) { 9 | callback(nextProps) 10 | return null 11 | } 12 | 13 | render() { 14 | return null 15 | } 16 | } 17 | 18 | polyfill(Sink) 19 | return Sink 20 | } 21 | 22 | export default createSink 23 | -------------------------------------------------------------------------------- /src/packages/recompose/defaultProps.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const defaultProps = props => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | const DefaultProps = ownerProps => factory(ownerProps) 8 | DefaultProps.defaultProps = props 9 | if (process.env.NODE_ENV !== 'production') { 10 | return setDisplayName(wrapDisplayName(BaseComponent, 'defaultProps'))( 11 | DefaultProps 12 | ) 13 | } 14 | return DefaultProps 15 | } 16 | 17 | export default defaultProps 18 | -------------------------------------------------------------------------------- /src/packages/recompose/flattenProp.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const flattenProp = propName => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | const FlattenProp = props => 8 | factory({ 9 | ...props, 10 | ...props[propName], 11 | }) 12 | 13 | if (process.env.NODE_ENV !== 'production') { 14 | return setDisplayName(wrapDisplayName(BaseComponent, 'flattenProp'))( 15 | FlattenProp 16 | ) 17 | } 18 | return FlattenProp 19 | } 20 | 21 | export default flattenProp 22 | -------------------------------------------------------------------------------- /src/packages/recompose/flydObservableConfig.js: -------------------------------------------------------------------------------- 1 | import $observable from 'symbol-observable' 2 | import flyd from 'flyd' 3 | 4 | const noop = () => {} 5 | 6 | const config = { 7 | fromESObservable: observable => { 8 | const stream = flyd.stream() 9 | const { unsubscribe } = observable.subscribe({ 10 | next: value => stream(value), 11 | error: error => stream({ error }), 12 | complete: () => stream.end(true), 13 | }) 14 | 15 | flyd.on(unsubscribe, stream.end) 16 | return stream 17 | }, 18 | 19 | toESObservable: stream => ({ 20 | subscribe: observer => { 21 | const sub = flyd.on(observer.next || noop, stream) 22 | flyd.on(_ => observer.complete(), sub.end) 23 | return { 24 | unsubscribe: () => sub.end(true), 25 | } 26 | }, 27 | [$observable]() { 28 | return this 29 | }, 30 | }), 31 | } 32 | 33 | export default config 34 | -------------------------------------------------------------------------------- /src/packages/recompose/fromRenderProps.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const fromRenderProps = ( 6 | RenderPropsComponent, 7 | propsMapper, 8 | renderPropName = 'children' 9 | ) => BaseComponent => { 10 | const baseFactory = React.createFactory(BaseComponent) 11 | const renderPropsFactory = React.createFactory(RenderPropsComponent) 12 | 13 | const FromRenderProps = ownerProps => 14 | renderPropsFactory({ 15 | [renderPropName]: (...props) => 16 | baseFactory({ ...ownerProps, ...propsMapper(...props) }), 17 | }) 18 | 19 | if (process.env.NODE_ENV !== 'production') { 20 | return setDisplayName(wrapDisplayName(BaseComponent, 'fromRenderProps'))( 21 | FromRenderProps 22 | ) 23 | } 24 | 25 | return FromRenderProps 26 | } 27 | 28 | export default fromRenderProps 29 | -------------------------------------------------------------------------------- /src/packages/recompose/getContext.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const getContext = contextTypes => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | const GetContext = (ownerProps, context) => 8 | factory({ 9 | ...ownerProps, 10 | ...context, 11 | }) 12 | 13 | GetContext.contextTypes = contextTypes 14 | 15 | if (process.env.NODE_ENV !== 'production') { 16 | return setDisplayName(wrapDisplayName(BaseComponent, 'getContext'))( 17 | GetContext 18 | ) 19 | } 20 | return GetContext 21 | } 22 | 23 | export default getContext 24 | -------------------------------------------------------------------------------- /src/packages/recompose/getDisplayName.js: -------------------------------------------------------------------------------- 1 | const getDisplayName = Component => { 2 | if (typeof Component === 'string') { 3 | return Component 4 | } 5 | 6 | if (!Component) { 7 | return undefined 8 | } 9 | 10 | return Component.displayName || Component.name || 'Component' 11 | } 12 | 13 | export default getDisplayName 14 | -------------------------------------------------------------------------------- /src/packages/recompose/hoistStatics.js: -------------------------------------------------------------------------------- 1 | import hoistNonReactStatics from 'hoist-non-react-statics' 2 | 3 | const hoistStatics = (higherOrderComponent, blacklist) => BaseComponent => { 4 | const NewComponent = higherOrderComponent(BaseComponent) 5 | hoistNonReactStatics(NewComponent, BaseComponent, blacklist) 6 | return NewComponent 7 | } 8 | 9 | export default hoistStatics 10 | -------------------------------------------------------------------------------- /src/packages/recompose/index.js: -------------------------------------------------------------------------------- 1 | // Higher-order component helpers 2 | export { default as mapProps } from './mapProps' 3 | export { default as withProps } from './withProps' 4 | export { default as withPropsOnChange } from './withPropsOnChange' 5 | export { default as withHandlers } from './withHandlers' 6 | export { default as defaultProps } from './defaultProps' 7 | export { default as renameProp } from './renameProp' 8 | export { default as renameProps } from './renameProps' 9 | export { default as flattenProp } from './flattenProp' 10 | export { default as withState } from './withState' 11 | export { default as withStateHandlers } from './withStateHandlers' 12 | export { default as withReducer } from './withReducer' 13 | export { default as branch } from './branch' 14 | export { default as renderComponent } from './renderComponent' 15 | export { default as renderNothing } from './renderNothing' 16 | export { default as shouldUpdate } from './shouldUpdate' 17 | export { default as pure } from './pure' 18 | export { default as onlyUpdateForKeys } from './onlyUpdateForKeys' 19 | export { default as onlyUpdateForPropTypes } from './onlyUpdateForPropTypes' 20 | export { default as withContext } from './withContext' 21 | export { default as getContext } from './getContext' 22 | export { default as lifecycle } from './lifecycle' 23 | export { default as toClass } from './toClass' 24 | export { default as toRenderProps } from './toRenderProps' 25 | export { default as fromRenderProps } from './fromRenderProps' 26 | 27 | // Static property helpers 28 | export { default as setStatic } from './setStatic' 29 | export { default as setPropTypes } from './setPropTypes' 30 | export { default as setDisplayName } from './setDisplayName' 31 | 32 | // Composition function 33 | export { default as compose } from './compose' 34 | 35 | // Other utils 36 | export { default as getDisplayName } from './getDisplayName' 37 | export { default as wrapDisplayName } from './wrapDisplayName' 38 | export { default as shallowEqual } from './shallowEqual' 39 | export { default as isClassComponent } from './isClassComponent' 40 | export { default as createSink } from './createSink' 41 | export { default as componentFromProp } from './componentFromProp' 42 | export { default as nest } from './nest' 43 | export { default as hoistStatics } from './hoistStatics' 44 | 45 | // Observable helpers 46 | export { 47 | default as componentFromStream, 48 | componentFromStreamWithConfig, 49 | } from './componentFromStream' 50 | export { 51 | default as mapPropsStream, 52 | mapPropsStreamWithConfig, 53 | } from './mapPropsStream' 54 | export { 55 | default as createEventHandler, 56 | createEventHandlerWithConfig, 57 | } from './createEventHandler' 58 | export { default as setObservableConfig } from './setObservableConfig' 59 | -------------------------------------------------------------------------------- /src/packages/recompose/index.js.flow: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /* @flow strict */ 4 | 5 | /** 6 | * 1) Types give additional constraint on a language, recompose was written on the untyped language 7 | * as a consequence of this fact 8 | * for some recompose HOCs is near impossible to add correct typings. 9 | * 2) flow sometimes does not work as expected. 10 | * 11 | * So any help and suggestions will be very appreciated. 12 | * 13 | * ----------------------------------------------------------------------------------- 14 | * Type definition of recompose HOCs are splitted into 2 parts, 15 | * "HOCs with good flow support" - in most cases you can use them without big issues, 16 | * see `test_${hocName}.js` for the idea. 17 | * Some known issues: 18 | * see test_mapProps.js - inference work but type errors are not detected in hocs 19 | * 20 | * SUPPORTED HOCs: 21 | * defaultProps, mapProps, withProps, withStateHandlers, withHandlers, pure, 22 | * onlyUpdateForKeys, shouldUpdate, renderNothing, renderComponent, branch, withPropsOnChange, 23 | * onlyUpdateForPropTypes, toClass, withContext, getContext, 24 | * setStatic, setPropTypes, setDisplayName, 25 | * ----------------------------------------------------------------------------------- 26 | * "TODO (UNSUPPORTED) HOCs" - you need to provide type information 27 | * (no automatic type inference), voodoo dancing etc 28 | * see `test_voodoo.js` for the idea 29 | * 30 | * remember that: 31 | * flattenProp,renameProp, renameProps can easily be replaced with withProps 32 | * withReducer, withState -> use withStateHandlers instead 33 | * lifecycle -> you don't need recompose if you need a lifecycle, just use React class instead 34 | * mapPropsStream -> see test_mapPropsStream.js 35 | * ----------------------------------------------------------------------------------- 36 | * 37 | * utils: 38 | * getDisplayName, wrapDisplayName, shallowEqual, 39 | * isClassComponent, createSink, componentFromProp, 40 | * nest, hoistStatics, 41 | */ 42 | 43 | //------------------- 44 | 45 | // ----------------------------------------------------------------- 46 | // Private declarations 47 | // ----------------------------------------------------------------- 48 | 49 | declare type ExtractToVoid = <A, B, C, R>( 50 | v: (a: A, b: B, c: C) => R 51 | ) => (A, B, C) => void 52 | 53 | declare type ExtractStateHandlersCodomain = <State, Enhanced, V>( 54 | v: (state: State, props: Enhanced) => V 55 | ) => V 56 | 57 | declare type ExtractHandlersCodomain = <Enhanced, V>( 58 | v: (props: Enhanced) => V 59 | ) => V 60 | 61 | declare type UnaryFn<A, R> = (a: A) => R 62 | 63 | // ----------------------------------------------------------------- 64 | // Public declarations 65 | // ----------------------------------------------------------------- 66 | 67 | export type Component<A> = React$ComponentType<A> 68 | 69 | export type HOC<Base, Enhanced> = UnaryFn<Component<Base>, Component<Enhanced>> 70 | 71 | declare export var compose: $Compose 72 | 73 | // --------------------------------------------------------------------------- 74 | // ----------------===<<<HOCs with good flow support>>>===-------------------- 75 | // --------------------------------------------------------------------------- 76 | 77 | declare export function defaultProps<Default, Enhanced>( 78 | defProps: Default 79 | ): HOC<{ ...$Exact<Enhanced>, ...Default }, Enhanced> 80 | 81 | declare export function mapProps<Base, Enhanced>( 82 | propsMapper: (ownerProps: Enhanced) => Base 83 | ): HOC<Base, Enhanced> 84 | 85 | declare export function withProps<BaseAdd, Enhanced>( 86 | propsMapper: ((ownerProps: Enhanced) => BaseAdd) | BaseAdd 87 | ): HOC<{ ...$Exact<Enhanced>, ...BaseAdd }, Enhanced> 88 | 89 | declare export function withStateHandlers< 90 | State, 91 | Enhanced, 92 | StateHandlers: { 93 | [key: string]: ( 94 | state: State, 95 | props: Enhanced 96 | ) => (...payload: any[]) => $Shape<State>, 97 | } 98 | >( 99 | initialState: ((props: Enhanced) => State) | State, 100 | stateUpdaters: StateHandlers 101 | ): HOC< 102 | { 103 | ...$Exact<Enhanced>, 104 | ...$Exact<State>, 105 | ...$ObjMap< 106 | $ObjMap<StateHandlers, ExtractStateHandlersCodomain>, 107 | ExtractToVoid 108 | >, 109 | }, 110 | Enhanced 111 | > 112 | 113 | declare export function withHandlers< 114 | Enhanced, 115 | Handlers: 116 | | (( 117 | props: Enhanced 118 | ) => { 119 | [key: string]: (props: Enhanced) => Function, 120 | }) 121 | | { 122 | [key: string]: (props: Enhanced) => Function, 123 | } 124 | >( 125 | handlers: ((props: Enhanced) => Handlers) | Handlers 126 | ): HOC< 127 | { 128 | ...$Exact<Enhanced>, 129 | ...$ObjMap<Handlers, ExtractHandlersCodomain>, 130 | }, 131 | Enhanced 132 | > 133 | 134 | declare export function pure<A>(a: Component<A>): Component<A> 135 | declare export function onlyUpdateForPropTypes<A>(a: Component<A>): Component<A> 136 | declare export function onlyUpdateForKeys<A>(Array<$Keys<A>>): HOC<A, A> 137 | declare export function shouldUpdate<A>( 138 | (props: A, nextProps: A) => boolean 139 | ): HOC<A, A> 140 | 141 | declare export function toClass<A>(a: Component<A>): Component<A> 142 | 143 | declare export function withContext<A, ContextPropTypes, ContextObj>( 144 | childContextTypes: ContextPropTypes, 145 | getChildContext: (props: A) => ContextObj 146 | ): HOC<A, A> 147 | 148 | declare export function getContext<CtxTypes, Enhanced>( 149 | contextTypes: CtxTypes 150 | ): HOC<{ ...$Exact<Enhanced>, ...CtxTypes }, Enhanced> 151 | 152 | /** 153 | * It's wrong declaration but having that renderNothing and renderComponent are somehow useless 154 | * outside branch enhancer, we just give it an id type 155 | * so common way of using branch like 156 | * `branch(testFn, renderNothing | renderComponent(Comp))` will work as expected. 157 | * Tests are placed at test_branch. 158 | */ 159 | declare export function renderNothing<A>(C: Component<A>): Component<A> 160 | declare export function renderComponent<A>(a: Component<A>): HOC<A, A> 161 | 162 | /** 163 | * We make an assumtion that left and right have the same type if exists 164 | */ 165 | declare export function branch<Base, Enhanced>( 166 | testFn: (props: Enhanced) => boolean, 167 | // not a HOC because of inference problems, this works but HOC<Base, Enhanced> is not 168 | left: (Component<Base>) => Component<Enhanced>, 169 | // I never use right part and it can be a problem with inference as should be same type as left 170 | right?: (Component<Base>) => Component<Enhanced> 171 | ): HOC<Base, Enhanced> 172 | 173 | // test_statics 174 | declare export function setStatic<A>(key: string, value: any): HOC<A, A> 175 | declare export function setPropTypes<A>(propTypes: Object): HOC<A, A> 176 | declare export function setDisplayName<A>(displayName: string): HOC<A, A> 177 | 178 | declare export function withPropsOnChange<BaseAdd, Enhanced>( 179 | shouldMapOrKeys: 180 | | ((props: Enhanced, nextProps: Enhanced) => boolean) 181 | | Array<$Keys<Enhanced>>, 182 | propsMapper: (ownerProps: Enhanced) => BaseAdd 183 | ): HOC<{ ...$Exact<Enhanced>, ...BaseAdd }, Enhanced> 184 | 185 | // --------------------------------------------------------------------------- 186 | // ----------------===<<<TODO (UNSUPPORTED) HOCs>>>===------------------------ 187 | // --------------------------------------------------------------------------- 188 | 189 | // use withProps instead 190 | declare export function flattenProp<Base, Enhanced>( 191 | propName: $Keys<Enhanced> 192 | ): HOC<Base, Enhanced> 193 | 194 | // use withProps instead 195 | declare export function renameProp<Base, Enhanced>( 196 | oldName: $Keys<Enhanced>, 197 | newName: $Keys<Base> 198 | ): HOC<Base, Enhanced> 199 | 200 | // use withProps instead 201 | declare export function renameProps<Base, Enhanced>(nameMap: { 202 | [key: $Keys<Enhanced>]: $Keys<Base>, 203 | }): HOC<Base, Enhanced> 204 | 205 | // use withStateHandlers instead 206 | declare export function withState<Base, Enhanced, T>( 207 | stateName: string, 208 | stateUpdaterName: string, 209 | initialState: T | ((props: Enhanced) => T) 210 | ): HOC<Base, Enhanced> 211 | 212 | // use withStateHandlers instead 213 | declare export function withReducer<A, B, Action, State>( 214 | stateName: string, 215 | dispatchName: string, 216 | reducer: (state: State, action: Action) => State, 217 | initialState: State 218 | ): HOC<A, B> 219 | 220 | // lifecycle use React instead 221 | declare export function lifecycle<A, B>(spec: Object): HOC<A, B> 222 | 223 | // Help needed, as explicitly providing the type 224 | // errors not detected, see TODO at test_mapPropsStream.js 225 | declare export function mapPropsStream<Base, Enhanced>( 226 | (props$: any) => any 227 | ): HOC<Base, Enhanced> 228 | 229 | // --------------------------------------------------------------------------- 230 | // -----------------------------===<<<Utils>>>===----------------------------- 231 | // --------------------------------------------------------------------------- 232 | 233 | declare export function getDisplayName<A>(C: Component<A>): string 234 | 235 | declare export function wrapDisplayName<A>( 236 | C: Component<A>, 237 | wrapperName: string 238 | ): string 239 | 240 | declare export function shallowEqual(objA: mixed, objB: mixed): boolean 241 | 242 | declare export function isClassComponent(value: any): boolean 243 | 244 | declare export function createSink<A>( 245 | callback: (props: A) => void 246 | ): Component<A> 247 | 248 | declare export function componentFromProp<A>(propName: string): Component<A> 249 | 250 | declare export function nest<A>( 251 | ...Components: Array<Component<any> | string> 252 | ): Component<A> 253 | 254 | declare export function hoistStatics<A, B, H: HOC<A, B>>(hoc: H): H 255 | 256 | declare export function componentFromStream<T>( 257 | (props$: any) => any 258 | ): T => React$Element<any> 259 | 260 | declare export function createEventHandler(): { 261 | stream: any, 262 | handler: Function, 263 | } 264 | 265 | declare export function toRenderProps<B, E>( 266 | hoc: HOC<B, E> 267 | ): React$ComponentType<{| 268 | ...$Exact<E>, 269 | children: (b: B) => React$Node, 270 | |}> 271 | 272 | declare export function fromRenderProps<B, E, RenderPropName, Props>( 273 | RenderPropsComponent: Component<{ 274 | [RenderPropName]: (...props: Props) => React$Node, 275 | }>, 276 | propsMapper: ((...props: Props) => B), 277 | renderPropName?: RenderPropName 278 | ): HOC<{ ...$Exact<E>, ...B }, E> 279 | -------------------------------------------------------------------------------- /src/packages/recompose/isClassComponent.js: -------------------------------------------------------------------------------- 1 | const isClassComponent = Component => 2 | Boolean( 3 | Component && 4 | Component.prototype && 5 | typeof Component.prototype.render === 'function' 6 | ) 7 | 8 | export default isClassComponent 9 | -------------------------------------------------------------------------------- /src/packages/recompose/kefirObservableConfig.js: -------------------------------------------------------------------------------- 1 | import Kefir from 'kefir' 2 | 3 | const config = { 4 | fromESObservable: Kefir.fromESObservable, 5 | toESObservable: stream => stream.toESObservable(), 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /src/packages/recompose/lifecycle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createFactory, Component } from 'react' 3 | import setDisplayName from './setDisplayName' 4 | import wrapDisplayName from './wrapDisplayName' 5 | 6 | const lifecycle = spec => BaseComponent => { 7 | const factory = createFactory(BaseComponent) 8 | 9 | if (process.env.NODE_ENV !== 'production' && spec.hasOwnProperty('render')) { 10 | console.error( 11 | 'lifecycle() does not support the render method; its behavior is to ' + 12 | 'pass all props and state to the base component.' 13 | ) 14 | } 15 | 16 | class Lifecycle extends Component { 17 | render() { 18 | return factory({ 19 | ...this.props, 20 | ...this.state, 21 | }) 22 | } 23 | } 24 | 25 | Object.keys(spec).forEach(hook => (Lifecycle.prototype[hook] = spec[hook])) 26 | 27 | if (process.env.NODE_ENV !== 'production') { 28 | return setDisplayName(wrapDisplayName(BaseComponent, 'lifecycle'))( 29 | Lifecycle 30 | ) 31 | } 32 | return Lifecycle 33 | } 34 | 35 | export default lifecycle 36 | -------------------------------------------------------------------------------- /src/packages/recompose/mapProps.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const mapProps = propsMapper => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | const MapProps = props => factory(propsMapper(props)) 8 | if (process.env.NODE_ENV !== 'production') { 9 | return setDisplayName(wrapDisplayName(BaseComponent, 'mapProps'))(MapProps) 10 | } 11 | return MapProps 12 | } 13 | 14 | export default mapProps 15 | -------------------------------------------------------------------------------- /src/packages/recompose/mapPropsStream.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import $observable from 'symbol-observable' 3 | import { componentFromStreamWithConfig } from './componentFromStream' 4 | import setDisplayName from './setDisplayName' 5 | import wrapDisplayName from './wrapDisplayName' 6 | import { config as globalConfig } from './setObservableConfig' 7 | 8 | const identity = t => t 9 | 10 | export const mapPropsStreamWithConfig = config => { 11 | const componentFromStream = componentFromStreamWithConfig({ 12 | fromESObservable: identity, 13 | toESObservable: identity, 14 | }) 15 | return transform => BaseComponent => { 16 | const factory = createFactory(BaseComponent) 17 | const { fromESObservable, toESObservable } = config 18 | return componentFromStream(props$ => ({ 19 | subscribe(observer) { 20 | const subscription = toESObservable( 21 | transform(fromESObservable(props$)) 22 | ).subscribe({ 23 | next: childProps => observer.next(factory(childProps)), 24 | }) 25 | return { 26 | unsubscribe: () => subscription.unsubscribe(), 27 | } 28 | }, 29 | [$observable]() { 30 | return this 31 | }, 32 | })) 33 | } 34 | } 35 | 36 | const mapPropsStream = transform => { 37 | const hoc = mapPropsStreamWithConfig(globalConfig)(transform) 38 | 39 | if (process.env.NODE_ENV !== 'production') { 40 | return BaseComponent => 41 | setDisplayName(wrapDisplayName(BaseComponent, 'mapPropsStream'))( 42 | hoc(BaseComponent) 43 | ) 44 | } 45 | return hoc 46 | } 47 | 48 | export default mapPropsStream 49 | -------------------------------------------------------------------------------- /src/packages/recompose/mostObservableConfig.js: -------------------------------------------------------------------------------- 1 | import { from, Stream } from 'most' 2 | 3 | const config = { 4 | fromESObservable: from || Stream.from, 5 | toESObservable: stream => stream, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /src/packages/recompose/nest.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import getDisplayName from './getDisplayName' 3 | 4 | const nest = (...Components) => { 5 | const factories = Components.map(createFactory) 6 | const Nest = ({ children, ...props }) => 7 | factories.reduceRight((child, factory) => factory(props, child), children) 8 | 9 | if (process.env.NODE_ENV !== 'production') { 10 | const displayNames = Components.map(getDisplayName) 11 | Nest.displayName = `nest(${displayNames.join(', ')})` 12 | } 13 | 14 | return Nest 15 | } 16 | 17 | export default nest 18 | -------------------------------------------------------------------------------- /src/packages/recompose/onlyUpdateForKeys.js: -------------------------------------------------------------------------------- 1 | import shouldUpdate from './shouldUpdate' 2 | import shallowEqual from './shallowEqual' 3 | import setDisplayName from './setDisplayName' 4 | import wrapDisplayName from './wrapDisplayName' 5 | import pick from './utils/pick' 6 | 7 | const onlyUpdateForKeys = propKeys => { 8 | const hoc = shouldUpdate( 9 | (props, nextProps) => 10 | !shallowEqual(pick(nextProps, propKeys), pick(props, propKeys)) 11 | ) 12 | 13 | if (process.env.NODE_ENV !== 'production') { 14 | return BaseComponent => 15 | setDisplayName(wrapDisplayName(BaseComponent, 'onlyUpdateForKeys'))( 16 | hoc(BaseComponent) 17 | ) 18 | } 19 | return hoc 20 | } 21 | 22 | export default onlyUpdateForKeys 23 | -------------------------------------------------------------------------------- /src/packages/recompose/onlyUpdateForPropTypes.js: -------------------------------------------------------------------------------- 1 | import onlyUpdateForKeys from './onlyUpdateForKeys' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | import getDisplayName from './getDisplayName' 5 | 6 | const onlyUpdateForPropTypes = BaseComponent => { 7 | const propTypes = BaseComponent.propTypes 8 | 9 | if (process.env.NODE_ENV !== 'production') { 10 | if (!propTypes) { 11 | /* eslint-disable */ 12 | console.error( 13 | 'A component without any `propTypes` was passed to ' + 14 | '`onlyUpdateForPropTypes()`. Check the implementation of the ' + 15 | `component with display name "${getDisplayName(BaseComponent)}".` 16 | ) 17 | /* eslint-enable */ 18 | } 19 | } 20 | 21 | const propKeys = Object.keys(propTypes || {}) 22 | const OnlyUpdateForPropTypes = onlyUpdateForKeys(propKeys)(BaseComponent) 23 | 24 | if (process.env.NODE_ENV !== 'production') { 25 | return setDisplayName( 26 | wrapDisplayName(BaseComponent, 'onlyUpdateForPropTypes') 27 | )(OnlyUpdateForPropTypes) 28 | } 29 | return OnlyUpdateForPropTypes 30 | } 31 | 32 | export default onlyUpdateForPropTypes 33 | -------------------------------------------------------------------------------- /src/packages/recompose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A React utility belt for function components and higher-order components", 3 | "keywords": [ 4 | "react", 5 | "higher-order", 6 | "components", 7 | "microcomponentization", 8 | "toolkit", 9 | "utilities", 10 | "composition" 11 | ], 12 | "main": "dist/Recompose.cjs.js", 13 | "module": "dist/Recompose.esm.js", 14 | "dependencies": { 15 | "@babel/runtime": "^7.0.0", 16 | "change-emitter": "^0.1.2", 17 | "hoist-non-react-statics": "^2.3.1", 18 | "react-lifecycles-compat": "^3.0.2", 19 | "symbol-observable": "^1.0.4" 20 | }, 21 | "peerDependencies": { 22 | "react": "^0.14.0 || ^15.0.0 || ^16.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/packages/recompose/pure.js: -------------------------------------------------------------------------------- 1 | import shouldUpdate from './shouldUpdate' 2 | import shallowEqual from './shallowEqual' 3 | import setDisplayName from './setDisplayName' 4 | import wrapDisplayName from './wrapDisplayName' 5 | 6 | const pure = BaseComponent => { 7 | const hoc = shouldUpdate( 8 | (props, nextProps) => !shallowEqual(props, nextProps) 9 | ) 10 | 11 | if (process.env.NODE_ENV !== 'production') { 12 | return setDisplayName(wrapDisplayName(BaseComponent, 'pure'))( 13 | hoc(BaseComponent) 14 | ) 15 | } 16 | 17 | return hoc(BaseComponent) 18 | } 19 | 20 | export default pure 21 | -------------------------------------------------------------------------------- /src/packages/recompose/renameProp.js: -------------------------------------------------------------------------------- 1 | import omit from './utils/omit' 2 | import mapProps from './mapProps' 3 | import setDisplayName from './setDisplayName' 4 | import wrapDisplayName from './wrapDisplayName' 5 | 6 | const renameProp = (oldName, newName) => { 7 | const hoc = mapProps(props => ({ 8 | ...omit(props, [oldName]), 9 | [newName]: props[oldName], 10 | })) 11 | if (process.env.NODE_ENV !== 'production') { 12 | return BaseComponent => 13 | setDisplayName(wrapDisplayName(BaseComponent, 'renameProp'))( 14 | hoc(BaseComponent) 15 | ) 16 | } 17 | return hoc 18 | } 19 | 20 | export default renameProp 21 | -------------------------------------------------------------------------------- /src/packages/recompose/renameProps.js: -------------------------------------------------------------------------------- 1 | import omit from './utils/omit' 2 | import pick from './utils/pick' 3 | import mapProps from './mapProps' 4 | import setDisplayName from './setDisplayName' 5 | import wrapDisplayName from './wrapDisplayName' 6 | 7 | const { keys } = Object 8 | 9 | const mapKeys = (obj, func) => 10 | keys(obj).reduce((result, key) => { 11 | const val = obj[key] 12 | /* eslint-disable no-param-reassign */ 13 | result[func(val, key)] = val 14 | /* eslint-enable no-param-reassign */ 15 | return result 16 | }, {}) 17 | 18 | const renameProps = nameMap => { 19 | const hoc = mapProps(props => ({ 20 | ...omit(props, keys(nameMap)), 21 | ...mapKeys(pick(props, keys(nameMap)), (_, oldName) => nameMap[oldName]), 22 | })) 23 | if (process.env.NODE_ENV !== 'production') { 24 | return BaseComponent => 25 | setDisplayName(wrapDisplayName(BaseComponent, 'renameProps'))( 26 | hoc(BaseComponent) 27 | ) 28 | } 29 | return hoc 30 | } 31 | 32 | export default renameProps 33 | -------------------------------------------------------------------------------- /src/packages/recompose/renderComponent.js: -------------------------------------------------------------------------------- 1 | import { createFactory } from 'react' 2 | import wrapDisplayName from './wrapDisplayName' 3 | 4 | const renderComponent = Component => _ => { 5 | const factory = createFactory(Component) 6 | const RenderComponent = props => factory(props) 7 | if (process.env.NODE_ENV !== 'production') { 8 | RenderComponent.displayName = wrapDisplayName(Component, 'renderComponent') 9 | } 10 | return RenderComponent 11 | } 12 | 13 | export default renderComponent 14 | -------------------------------------------------------------------------------- /src/packages/recompose/renderNothing.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | 3 | class Nothing extends Component { 4 | render() { 5 | return null 6 | } 7 | } 8 | 9 | const renderNothing = _ => Nothing 10 | 11 | export default renderNothing 12 | -------------------------------------------------------------------------------- /src/packages/recompose/rxjs4ObservableConfig.js: -------------------------------------------------------------------------------- 1 | import $observable from 'symbol-observable' 2 | import Rx from 'rx' 3 | 4 | const config = { 5 | fromESObservable: observable => 6 | Rx.Observable.create(observer => { 7 | const { unsubscribe } = observable.subscribe({ 8 | next: val => observer.onNext(val), 9 | error: error => observer.onError(error), 10 | complete: () => observer.onCompleted(), 11 | }) 12 | return unsubscribe 13 | }), 14 | toESObservable: rxObservable => ({ 15 | subscribe: observer => { 16 | const subscription = rxObservable.subscribe( 17 | val => observer.next(val), 18 | error => observer.error(error), 19 | () => observer.complete() 20 | ) 21 | return { unsubscribe: () => subscription.dispose() } 22 | }, 23 | [$observable]() { 24 | return this 25 | }, 26 | }), 27 | } 28 | 29 | export default config 30 | -------------------------------------------------------------------------------- /src/packages/recompose/rxjsObservableConfig.js: -------------------------------------------------------------------------------- 1 | import Rx from 'rxjs' 2 | 3 | const config = { 4 | fromESObservable: Rx.Observable.from, 5 | toESObservable: stream => stream, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /src/packages/recompose/setDisplayName.js: -------------------------------------------------------------------------------- 1 | import setStatic from './setStatic' 2 | 3 | const setDisplayName = displayName => setStatic('displayName', displayName) 4 | 5 | export default setDisplayName 6 | -------------------------------------------------------------------------------- /src/packages/recompose/setObservableConfig.js: -------------------------------------------------------------------------------- 1 | let _config = { 2 | fromESObservable: null, 3 | toESObservable: null, 4 | } 5 | 6 | const configureObservable = c => { 7 | _config = c 8 | } 9 | 10 | export const config = { 11 | fromESObservable: observable => 12 | typeof _config.fromESObservable === 'function' 13 | ? _config.fromESObservable(observable) 14 | : observable, 15 | toESObservable: stream => 16 | typeof _config.toESObservable === 'function' 17 | ? _config.toESObservable(stream) 18 | : stream, 19 | } 20 | 21 | export default configureObservable 22 | -------------------------------------------------------------------------------- /src/packages/recompose/setPropTypes.js: -------------------------------------------------------------------------------- 1 | import setStatic from './setStatic' 2 | 3 | const setPropTypes = propTypes => setStatic('propTypes', propTypes) 4 | 5 | export default setPropTypes 6 | -------------------------------------------------------------------------------- /src/packages/recompose/setStatic.js: -------------------------------------------------------------------------------- 1 | const setStatic = (key, value) => BaseComponent => { 2 | /* eslint-disable no-param-reassign */ 3 | BaseComponent[key] = value 4 | /* eslint-enable no-param-reassign */ 5 | return BaseComponent 6 | } 7 | 8 | export default setStatic 9 | -------------------------------------------------------------------------------- /src/packages/recompose/shallowEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @providesModule shallowEqual 8 | * @typechecks 9 | */ 10 | 11 | /* eslint-disable no-self-compare */ 12 | 13 | const hasOwnProperty = Object.prototype.hasOwnProperty 14 | 15 | /** 16 | * inlined Object.is polyfill to avoid requiring consumers ship their own 17 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 18 | */ 19 | function is(x, y) { 20 | // SameValue algorithm 21 | if (x === y) { 22 | // Steps 1-5, 7-10 23 | // Steps 6.b-6.e: +0 != -0 24 | // Added the nonzero y check to make Flow happy, but it is redundant 25 | return x !== 0 || y !== 0 || 1 / x === 1 / y 26 | } 27 | // Step 6.a: NaN == NaN 28 | return x !== x && y !== y 29 | } 30 | 31 | /** 32 | * Performs equality by iterating through keys on an object and returning false 33 | * when any key has values which are not strictly equal between the arguments. 34 | * Returns true when the values of all keys are strictly equal. 35 | */ 36 | function shallowEqual(objA, objB) { 37 | if (is(objA, objB)) { 38 | return true 39 | } 40 | 41 | if ( 42 | typeof objA !== 'object' || 43 | objA === null || 44 | typeof objB !== 'object' || 45 | objB === null 46 | ) { 47 | return false 48 | } 49 | 50 | const keysA = Object.keys(objA) 51 | const keysB = Object.keys(objB) 52 | 53 | if (keysA.length !== keysB.length) { 54 | return false 55 | } 56 | 57 | // Test for A's keys different from B. 58 | for (let i = 0; i < keysA.length; i++) { 59 | if ( 60 | !hasOwnProperty.call(objB, keysA[i]) || 61 | !is(objA[keysA[i]], objB[keysA[i]]) 62 | ) { 63 | return false 64 | } 65 | } 66 | 67 | return true 68 | } 69 | 70 | export default shallowEqual 71 | -------------------------------------------------------------------------------- /src/packages/recompose/shouldUpdate.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const shouldUpdate = test => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | class ShouldUpdate extends Component { 8 | shouldComponentUpdate(nextProps) { 9 | return test(this.props, nextProps) 10 | } 11 | 12 | render() { 13 | return factory(this.props) 14 | } 15 | } 16 | 17 | if (process.env.NODE_ENV !== 'production') { 18 | return setDisplayName(wrapDisplayName(BaseComponent, 'shouldUpdate'))( 19 | ShouldUpdate 20 | ) 21 | } 22 | return ShouldUpdate 23 | } 24 | 25 | export default shouldUpdate 26 | -------------------------------------------------------------------------------- /src/packages/recompose/toClass.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import getDisplayName from './getDisplayName' 3 | import isClassComponent from './isClassComponent' 4 | 5 | const toClass = baseComponent => 6 | isClassComponent(baseComponent) 7 | ? baseComponent 8 | : class ToClass extends Component { 9 | static displayName = getDisplayName(baseComponent) 10 | static propTypes = baseComponent.propTypes 11 | static contextTypes = baseComponent.contextTypes 12 | static defaultProps = baseComponent.defaultProps 13 | render() { 14 | if (typeof baseComponent === 'string') { 15 | return React.createElement(baseComponent, this.props) 16 | } 17 | return baseComponent(this.props, this.context) 18 | } 19 | } 20 | export default toClass 21 | -------------------------------------------------------------------------------- /src/packages/recompose/toRenderProps.js: -------------------------------------------------------------------------------- 1 | export default function toRenderProps(hoc) { 2 | const RenderPropsComponent = props => props.children(props) 3 | return hoc(RenderPropsComponent) 4 | } 5 | -------------------------------------------------------------------------------- /src/packages/recompose/utils/mapValues.js: -------------------------------------------------------------------------------- 1 | const mapValues = (obj, func) => { 2 | const result = {} 3 | /* eslint-disable no-restricted-syntax */ 4 | for (const key in obj) { 5 | if (obj.hasOwnProperty(key)) { 6 | result[key] = func(obj[key], key) 7 | } 8 | } 9 | /* eslint-enable no-restricted-syntax */ 10 | return result 11 | } 12 | 13 | export default mapValues 14 | -------------------------------------------------------------------------------- /src/packages/recompose/utils/omit.js: -------------------------------------------------------------------------------- 1 | const omit = (obj, keys) => { 2 | const { ...rest } = obj 3 | for (let i = 0; i < keys.length; i++) { 4 | const key = keys[i] 5 | if (rest.hasOwnProperty(key)) { 6 | delete rest[key] 7 | } 8 | } 9 | return rest 10 | } 11 | 12 | export default omit 13 | -------------------------------------------------------------------------------- /src/packages/recompose/utils/pick.js: -------------------------------------------------------------------------------- 1 | const pick = (obj, keys) => { 2 | const result = {} 3 | for (let i = 0; i < keys.length; i++) { 4 | const key = keys[i] 5 | if (obj.hasOwnProperty(key)) { 6 | result[key] = obj[key] 7 | } 8 | } 9 | return result 10 | } 11 | 12 | export default pick 13 | -------------------------------------------------------------------------------- /src/packages/recompose/withContext.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const withContext = (childContextTypes, getChildContext) => BaseComponent => { 6 | const factory = createFactory(BaseComponent) 7 | class WithContext extends Component { 8 | getChildContext = () => getChildContext(this.props) 9 | 10 | render() { 11 | return factory(this.props) 12 | } 13 | } 14 | 15 | WithContext.childContextTypes = childContextTypes 16 | 17 | if (process.env.NODE_ENV !== 'production') { 18 | return setDisplayName(wrapDisplayName(BaseComponent, 'withContext'))( 19 | WithContext 20 | ) 21 | } 22 | return WithContext 23 | } 24 | 25 | export default withContext 26 | -------------------------------------------------------------------------------- /src/packages/recompose/withHandlers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createFactory, Component } from 'react' 3 | import setDisplayName from './setDisplayName' 4 | import wrapDisplayName from './wrapDisplayName' 5 | import mapValues from './utils/mapValues' 6 | 7 | const withHandlers = handlers => BaseComponent => { 8 | const factory = createFactory(BaseComponent) 9 | class WithHandlers extends Component { 10 | handlers = mapValues( 11 | typeof handlers === 'function' ? handlers(this.props) : handlers, 12 | createHandler => (...args) => { 13 | const handler = createHandler(this.props) 14 | 15 | if ( 16 | process.env.NODE_ENV !== 'production' && 17 | typeof handler !== 'function' 18 | ) { 19 | console.error( 20 | // eslint-disable-line no-console 21 | 'withHandlers(): Expected a map of higher-order functions. ' + 22 | 'Refer to the docs for more info.' 23 | ) 24 | } 25 | 26 | return handler(...args) 27 | } 28 | ) 29 | 30 | render() { 31 | return factory({ 32 | ...this.props, 33 | ...this.handlers, 34 | }) 35 | } 36 | } 37 | 38 | if (process.env.NODE_ENV !== 'production') { 39 | return setDisplayName(wrapDisplayName(BaseComponent, 'withHandlers'))( 40 | WithHandlers 41 | ) 42 | } 43 | return WithHandlers 44 | } 45 | 46 | export default withHandlers 47 | -------------------------------------------------------------------------------- /src/packages/recompose/withProps.js: -------------------------------------------------------------------------------- 1 | import wrapDisplayName from './wrapDisplayName' 2 | import setDisplayName from './setDisplayName' 3 | import mapProps from './mapProps' 4 | 5 | const withProps = input => { 6 | const hoc = mapProps(props => ({ 7 | ...props, 8 | ...(typeof input === 'function' ? input(props) : input), 9 | })) 10 | if (process.env.NODE_ENV !== 'production') { 11 | return BaseComponent => 12 | setDisplayName(wrapDisplayName(BaseComponent, 'withProps'))( 13 | hoc(BaseComponent) 14 | ) 15 | } 16 | return hoc 17 | } 18 | 19 | export default withProps 20 | -------------------------------------------------------------------------------- /src/packages/recompose/withPropsOnChange.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import { polyfill } from 'react-lifecycles-compat' 3 | import pick from './utils/pick' 4 | import shallowEqual from './shallowEqual' 5 | import setDisplayName from './setDisplayName' 6 | import wrapDisplayName from './wrapDisplayName' 7 | 8 | const withPropsOnChange = (shouldMapOrKeys, propsMapper) => BaseComponent => { 9 | const factory = createFactory(BaseComponent) 10 | const shouldMap = 11 | typeof shouldMapOrKeys === 'function' 12 | ? shouldMapOrKeys 13 | : (props, nextProps) => 14 | !shallowEqual( 15 | pick(props, shouldMapOrKeys), 16 | pick(nextProps, shouldMapOrKeys) 17 | ) 18 | 19 | class WithPropsOnChange extends Component { 20 | state = { 21 | computedProps: propsMapper(this.props), 22 | prevProps: this.props, 23 | } 24 | 25 | static getDerivedStateFromProps(nextProps, prevState) { 26 | if (shouldMap(prevState.prevProps, nextProps)) { 27 | return { 28 | computedProps: propsMapper(nextProps), 29 | prevProps: nextProps, 30 | } 31 | } 32 | 33 | return { 34 | prevProps: nextProps, 35 | } 36 | } 37 | 38 | render() { 39 | return factory({ 40 | ...this.props, 41 | ...this.state.computedProps, 42 | }) 43 | } 44 | } 45 | 46 | polyfill(WithPropsOnChange) 47 | 48 | if (process.env.NODE_ENV !== 'production') { 49 | return setDisplayName(wrapDisplayName(BaseComponent, 'withPropsOnChange'))( 50 | WithPropsOnChange 51 | ) 52 | } 53 | 54 | return WithPropsOnChange 55 | } 56 | 57 | export default withPropsOnChange 58 | -------------------------------------------------------------------------------- /src/packages/recompose/withReducer.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const noop = () => {} 6 | 7 | const withReducer = ( 8 | stateName, 9 | dispatchName, 10 | reducer, 11 | initialState 12 | ) => BaseComponent => { 13 | const factory = createFactory(BaseComponent) 14 | class WithReducer extends Component { 15 | state = { 16 | stateValue: this.initializeStateValue(), 17 | } 18 | 19 | initializeStateValue() { 20 | if (initialState !== undefined) { 21 | return typeof initialState === 'function' 22 | ? initialState(this.props) 23 | : initialState 24 | } 25 | return reducer(undefined, { type: '@@recompose/INIT' }) 26 | } 27 | 28 | dispatch = (action, callback = noop) => 29 | this.setState( 30 | ({ stateValue }) => ({ 31 | stateValue: reducer(stateValue, action), 32 | }), 33 | () => callback(this.state.stateValue) 34 | ) 35 | 36 | render() { 37 | return factory({ 38 | ...this.props, 39 | [stateName]: this.state.stateValue, 40 | [dispatchName]: this.dispatch, 41 | }) 42 | } 43 | } 44 | 45 | if (process.env.NODE_ENV !== 'production') { 46 | return setDisplayName(wrapDisplayName(BaseComponent, 'withReducer'))( 47 | WithReducer 48 | ) 49 | } 50 | return WithReducer 51 | } 52 | 53 | export default withReducer 54 | -------------------------------------------------------------------------------- /src/packages/recompose/withState.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | 5 | const withState = ( 6 | stateName, 7 | stateUpdaterName, 8 | initialState 9 | ) => BaseComponent => { 10 | const factory = createFactory(BaseComponent) 11 | class WithState extends Component { 12 | state = { 13 | stateValue: 14 | typeof initialState === 'function' 15 | ? initialState(this.props) 16 | : initialState, 17 | } 18 | 19 | updateStateValue = (updateFn, callback) => 20 | this.setState( 21 | ({ stateValue }) => ({ 22 | stateValue: 23 | typeof updateFn === 'function' ? updateFn(stateValue) : updateFn, 24 | }), 25 | callback 26 | ) 27 | 28 | render() { 29 | return factory({ 30 | ...this.props, 31 | [stateName]: this.state.stateValue, 32 | [stateUpdaterName]: this.updateStateValue, 33 | }) 34 | } 35 | } 36 | 37 | if (process.env.NODE_ENV !== 'production') { 38 | return setDisplayName(wrapDisplayName(BaseComponent, 'withState'))( 39 | WithState 40 | ) 41 | } 42 | return WithState 43 | } 44 | 45 | export default withState 46 | -------------------------------------------------------------------------------- /src/packages/recompose/withStateHandlers.js: -------------------------------------------------------------------------------- 1 | import { createFactory, Component } from 'react' 2 | import setDisplayName from './setDisplayName' 3 | import wrapDisplayName from './wrapDisplayName' 4 | import mapValues from './utils/mapValues' 5 | 6 | const withStateHandlers = (initialState, stateUpdaters) => BaseComponent => { 7 | const factory = createFactory(BaseComponent) 8 | 9 | class WithStateHandlers extends Component { 10 | state = typeof initialState === 'function' 11 | ? initialState(this.props) 12 | : initialState 13 | 14 | stateUpdaters = mapValues( 15 | stateUpdaters, 16 | handler => (mayBeEvent, ...args) => { 17 | // Having that functional form of setState can be called async 18 | // we need to persist SyntheticEvent 19 | if (mayBeEvent && typeof mayBeEvent.persist === 'function') { 20 | mayBeEvent.persist() 21 | } 22 | 23 | this.setState((state, props) => 24 | handler(state, props)(mayBeEvent, ...args) 25 | ) 26 | } 27 | ) 28 | 29 | render() { 30 | return factory({ 31 | ...this.props, 32 | ...this.state, 33 | ...this.stateUpdaters, 34 | }) 35 | } 36 | } 37 | 38 | if (process.env.NODE_ENV !== 'production') { 39 | return setDisplayName(wrapDisplayName(BaseComponent, 'withStateHandlers'))( 40 | WithStateHandlers 41 | ) 42 | } 43 | return WithStateHandlers 44 | } 45 | 46 | export default withStateHandlers 47 | -------------------------------------------------------------------------------- /src/packages/recompose/wrapDisplayName.js: -------------------------------------------------------------------------------- 1 | import getDisplayName from './getDisplayName' 2 | 3 | const wrapDisplayName = (BaseComponent, hocName) => 4 | `${hocName}(${getDisplayName(BaseComponent)})` 5 | 6 | export default wrapDisplayName 7 | -------------------------------------------------------------------------------- /src/packages/recompose/xstreamObservableConfig.js: -------------------------------------------------------------------------------- 1 | import $observable from 'symbol-observable' 2 | import xstream from 'xstream' 3 | 4 | const noop = () => {} 5 | 6 | const config = { 7 | fromESObservable: observable => 8 | xstream.create({ 9 | subscription: null, 10 | start(listener) { 11 | this.subscription = observable.subscribe(listener) 12 | }, 13 | stop() { 14 | this.subscription.unsubscribe() 15 | }, 16 | }), 17 | toESObservable: stream => ({ 18 | subscribe: observer => { 19 | const listener = { 20 | next: observer.next || noop, 21 | error: observer.error || noop, 22 | complete: observer.complete || noop, 23 | } 24 | stream.addListener(listener) 25 | return { 26 | unsubscribe: () => stream.removeListener(listener), 27 | } 28 | }, 29 | [$observable]() { 30 | return this 31 | }, 32 | }), 33 | } 34 | 35 | export default config 36 | -------------------------------------------------------------------------------- /src/packages/recompose/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.0.0": 6 | version "7.0.0" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" 8 | integrity sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA== 9 | dependencies: 10 | regenerator-runtime "^0.12.0" 11 | 12 | change-emitter@^0.1.2: 13 | version "0.1.6" 14 | resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" 15 | integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= 16 | 17 | hoist-non-react-statics@^2.3.1: 18 | version "2.3.1" 19 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" 20 | integrity sha1-ND24TGAYxlB3iJgkATWhQg7iLOA= 21 | 22 | react-lifecycles-compat@^3.0.2: 23 | version "3.0.2" 24 | resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz#7279047275bd727a912e25f734c0559527e84eff" 25 | integrity sha512-pbZOSMVVkvppW7XRn9fcHK5OgEDnYLwMva7P6TgS44/SN9uGGjfh3Z1c8tomO+y4IsHQ6Fsz2EGwmE7sMeNZgQ== 26 | 27 | regenerator-runtime@^0.12.0: 28 | version "0.12.0" 29 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.0.tgz#8052ac952d85b10f3425192cd0c53f45cf65c6cb" 30 | integrity sha512-SpV2LhF5Dm9UYMEprB3WwsBnWwqTrmjrm2UZb42cl2G02WVGgx7Mg8aa9pdLEKp6hZ+/abcMc2NxKA8f02EG2w== 31 | 32 | symbol-observable@^1.0.4: 33 | version "1.0.4" 34 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 35 | integrity sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0= 36 | -------------------------------------------------------------------------------- /src/packages/rx-recompose/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # Flow support for recompose 2 | 3 | ## How it works 4 | 5 | In most cases all you need is to declare a props type of enhanced Component. 6 | Flow will infer all other types you need. 7 | 8 | Example: 9 | 10 | ```javascript 11 | import type { HOC } from 'recompose'; 12 | 13 | type EnhancedComponentProps = { 14 | text?: string, 15 | }; 16 | 17 | const baseComponent = ({ text }) => <div>{text}</div>; 18 | 19 | const enhance:HOC<*, EnhancedComponentProps> = compose( 20 | defaultProps({ 21 | text: 'world', 22 | }), 23 | withProps(({ text }) => ({ 24 | text: `Hello ${text}` 25 | })) 26 | ); 27 | 28 | export default enhance(baseComponent); 29 | 30 | ``` 31 | 32 | See it in action. 33 | 34 |  35 | 36 | ## How to start 37 | 38 | The easiest way is to start from example. 39 | 40 | Look at [this](http://grader-meets-16837.netlify.com/) app [source](./flow-example) 41 | 42 | Look at [library definitions and tests](./flow-typed/recompose_v0.24.x-/flow_v0.49.x-) 43 | 44 | To add into project you can download library definitions [here](./flow-typed/recompose_v0.24.x/flow_v0.53.x-/recompose_v0.24.x.js) and add a path to it into `[lib]` section of `.flowconfig`. 45 | 46 | Or use [flow-typed](https://github.com/flowtype/flow-typed) 47 | 48 | ```bash 49 | flow-typed install recompose@0.24.x 50 | ``` 51 | 52 | ## Support 53 | 54 | Type definitions of recompose HOCs are splitted into 2 parts. 55 | 56 | ### Part 1 - HOCs with good flow support 57 | 58 | In most cases you can use them without big issues. 59 | Type inference and errors detection works near well. 60 | 61 | These HOCs are: *defaultProps, mapProps, withProps, withStateHandlers, withHandlers, pure, onlyUpdateForKeys, shouldUpdate, renderNothing, renderComponent, branch, withPropsOnChange, onlyUpdateForPropTypes, toClass, withContext, getContext, setStatic, setPropTypes, setDisplayName* 62 | 63 | #### Known issues for "good" HOCs 64 | 65 | see `test_mapProps.js` - inference work but type errors are not detected in hocs 66 | 67 | ### Part 2 - other HOCs 68 | 69 | To use these HOCs - you need to provide type information (no automatic type inference). 70 | You must be a good voodoo dancer. 71 | 72 | See `test_voodoo.js` for the idea. 73 | 74 | Some recomendations: 75 | 76 | - *flattenProp,renameProp, renameProps* can easily be replaced with _withProps_ 77 | - *withReducer, withState* -> use _withStateHandlers_ instead 78 | - _lifecycle_ -> you don't need recompose if you need a _lifecycle_, just use React class instead 79 | - _mapPropsStream_ -> see `test_mapPropsStream.js` 80 | 81 | #### Known issues for above HOCs 82 | 83 | See `test_voodoo.js`, `test_mapPropsStream.js` 84 | 85 | ### Utils 86 | 87 | *getDisplayName, wrapDisplayName, shallowEqual,isClassComponent, createSink, componentFromProp, nest, hoistStatics.* 88 | 89 | ### Articles 90 | 91 | [Typing Higher-order Components in Recompose With Flow](https://medium.com/flow-type/flow-support-in-recompose-1b76f58f4cfc) 92 | 93 | ### Faq 94 | 95 | Why to use existential type with `HOC<*, Blbla>` isn't it possible to avoid this? 96 | 97 | *I tried to use type alias but haven't found how to make it work.* 98 | 99 | ## Thanks 100 | 101 | Big thanks to [@gcanti](https://github.com/gcanti) for his work on PR [#241](https://github.com/acdlite/recompose/pull/241), it was nice and clear base for current definitions. 102 | -------------------------------------------------------------------------------- /types/flow-example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /types/flow-example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/eslint-plugin-jsx-a11y/.* 3 | # TODO: remove after react-motion will be updated with new flow typedefs 4 | .*/node_modules/react-motion/lib/* 5 | 6 | [include] 7 | 8 | [libs] 9 | ../flow-typed/recompose_v0.24.x/flow_v0.55.x-/recompose_v0.24.x.js 10 | 11 | [options] 12 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError 13 | 14 | [lints] 15 | -------------------------------------------------------------------------------- /types/flow-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /types/flow-example/README.md: -------------------------------------------------------------------------------- 1 | # recompose with flow usage example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | -------------------------------------------------------------------------------- /types/flow-example/flow-typed/glamor.js: -------------------------------------------------------------------------------- 1 | declare module 'glamor' { 2 | declare type CSSF = (...style: Object[]) => Object 3 | declare type CSS = { 4 | insert: (css: string) => void, 5 | // hack https://github.com/facebook/flow/issues/2966 6 | $call: (...style: Object[]) => Object, 7 | } 8 | 9 | declare export var css: CSS 10 | } 11 | -------------------------------------------------------------------------------- /types/flow-example/flow-typed/react-motion.js: -------------------------------------------------------------------------------- 1 | // TODO: remove after react-motion will be updated with new flow typedefs 2 | declare module 'react-motion' { 3 | declare export function spring<A>( 4 | val: number, 5 | config?: A 6 | ): { val: number, ...$Exact<A> } 7 | 8 | declare export var TransitionMotion: React$ComponentType<{ styles: any }> 9 | } 10 | -------------------------------------------------------------------------------- /types/flow-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coolmenu", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "change-emitter": "^0.1.6", 7 | "flow": "^0.2.3", 8 | "flow-bin": "^0.55.0", 9 | "glamor": "^3.0.0-3", 10 | "glamor-reset": "^3.0.0-2", 11 | "hoist-non-react-statics": "^2.0.0", 12 | "react": "^15.6.1", 13 | "react-dom": "^15.6.1", 14 | "react-motion": "^0.5.0", 15 | "recompose": "^0.24.0" 16 | }, 17 | "devDependencies": { 18 | "react-scripts": "1.0.10" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "flow": "flow" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /types/flow-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acdlite/recompose/3db12ce7121a050b533476958ff3d66ded1c4bb8/types/flow-example/public/favicon.ico -------------------------------------------------------------------------------- /types/flow-example/public/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html lang="en"> 3 | 4 | <head> 5 | <meta charset="utf-8"> 6 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 7 | <meta name="theme-color" content="#000000"> 8 | <!-- 9 | manifest.json provides metadata used when your web app is added to the 10 | homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ 11 | --> 12 | <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> 13 | <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> 14 | <!-- 15 | Notice the use of %PUBLIC_URL% in the tags above. 16 | It will be replaced with the URL of the `public` folder during the build. 17 | Only files inside the `public` folder can be referenced from the HTML. 18 | 19 | Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will 20 | work correctly both with client-side routing and a non-root public URL. 21 | Learn how to configure a non-root public URL by running `npm run build`. 22 | --> 23 | <title>Recompose coolmenu</title> 24 | </head> 25 | 26 | <body> 27 | <noscript> 28 | You need to enable JavaScript to run this app. 29 | </noscript> 30 | <div id="root"></div> 31 | <!-- 32 | This HTML file is a template. 33 | If you open it directly in the browser, you will see an empty page. 34 | 35 | You can add webfonts, meta tags, or analytics to this file. 36 | The build step will place the bundled scripts into the <body> tag. 37 | 38 | To begin the development, run `npm start` or `yarn start`. 39 | To create a production bundle, use `npm run build` or `yarn build`. 40 | --> 41 | </body> 42 | 43 | </html> -------------------------------------------------------------------------------- /types/flow-example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /types/flow-example/src/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { css } from 'glamor' 4 | import { compose, defaultProps } from 'recompose' 5 | import MouseDetector from './MouseDetector' 6 | import ItemsAnimator from './ItemsAnimator' 7 | import type { HOC } from 'recompose' 8 | import type { MousePosition } from './MouseDetector' 9 | 10 | // Enhanced component props type 11 | type AppProps = {} 12 | 13 | const app = ({ styles, items }) => 14 | <div {...styles.app}> 15 | <div {...styles.header}> 16 | <a href={'https://github.com/acdlite/recompose/types/flow-example'}> 17 | Coolmenu 18 | </a>{' '} 19 | <span>recompose flow typed example</span> 20 | </div> 21 | <div {...styles.content}> 22 | <MouseDetector> 23 | {({ x, y }: MousePosition) => 24 | <ItemsAnimator items={items} spacing={-10} mousePos={{ x, y }} />} 25 | </MouseDetector> 26 | </div> 27 | <div {...styles.footer}> 28 | <span>created by</span>{' '} 29 | <a href={'https://github.com/istarkov'}>Ivan Starkov</a> 30 | </div> 31 | </div> 32 | 33 | const appEnhancer: HOC<*, AppProps> = compose( 34 | defaultProps({ 35 | items: [ 36 | { id: 1, title: 'Recompose', color: '#FFB02E' }, 37 | { id: 2, title: 'Has', color: '#FFB02E' }, 38 | { id: 3, title: 'Flow', color: '#FFB02E' }, 39 | { id: 4, title: 'Support', color: '#FFB02E' }, 40 | { id: 5, title: 'Now!', color: '#FFB02E' }, 41 | ], 42 | styles: { 43 | app: css({ 44 | display: 'flex', 45 | flex: 1, 46 | flexDirection: 'column', 47 | }), 48 | header: css({ 49 | padding: '1rem 5rem', 50 | borderBottom: '1px solid rgba(255,255,255,0.2)', 51 | fontSize: '1.2rem', 52 | backgroundColor: 'rgba(0,0,0,0.05)', 53 | '>SPAN': { 54 | fontSize: '0.8rem', 55 | }, 56 | '>A': { 57 | color: '#CCCCFF', 58 | textDecoration: 'none', 59 | }, 60 | }), 61 | content: css({ 62 | display: 'flex', 63 | flex: 1, 64 | alignItems: 'center', 65 | justifyContent: 'center', 66 | }), 67 | footer: css({ 68 | padding: '0.5rem 5rem', 69 | marginTop: 'auto', 70 | borderTop: '1px solid rgba(255,255,255,0.2)', 71 | backgroundColor: 'rgba(0,0,0,0.05)', 72 | textAlign: 'right', 73 | '>SPAN': { 74 | fontSize: '0.8rem', 75 | }, 76 | '>A': { 77 | color: '#CCCCFF', 78 | textDecoration: 'none', 79 | }, 80 | }), 81 | }, 82 | }) 83 | ) 84 | 85 | export default appEnhancer(app) 86 | -------------------------------------------------------------------------------- /types/flow-example/src/Item.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from 'react' 4 | import { css } from 'glamor' 5 | import { compose, defaultProps, withProps, withPropsOnChange } from 'recompose' 6 | import type { HOC } from 'recompose' 7 | 8 | // Props type of enhanced component 9 | // It's the only props type we need to declare using supporting recompose enhancers 10 | type ItemProps = { 11 | title: string, 12 | styles?: { 13 | component: Object, 14 | title: Object, 15 | border: Object, 16 | }, 17 | size?: number, 18 | x?: number, 19 | y?: number, 20 | borderCount?: number, 21 | hovered?: boolean, 22 | color?: string, 23 | textColor?: string, 24 | hoveredColor?: string, 25 | } 26 | 27 | const item = ({ title, componentDynamicStyle, styles, borders }) => 28 | <div {...styles.component} style={componentDynamicStyle}> 29 | <div {...styles.title}> 30 | {title} 31 | </div> 32 | {borders.map(({ style, id }) => 33 | <div key={id} style={style} {...styles.border} /> 34 | )} 35 | </div> 36 | 37 | // set existential * type for base component, 38 | // flow is smart enough to infer base component and enhancers props types 39 | const enhanceItem: HOC<*, ItemProps> = compose( 40 | defaultProps({ 41 | title: '', 42 | styles: { 43 | component: css({ 44 | position: 'absolute', 45 | display: 'flex', 46 | }), 47 | title: css({ 48 | margin: 'auto', 49 | color: 'deeppink', 50 | fontWeight: '600', 51 | }), 52 | border: css({ 53 | position: 'absolute', 54 | border: '1px solid deeppink', 55 | left: 0, 56 | right: 0, 57 | top: 0, 58 | bottom: 0, 59 | borderRadius: 5, 60 | cursor: 'pointer', 61 | }), 62 | }, 63 | size: 100, 64 | x: 0, 65 | y: 0, 66 | borderCount: 9, 67 | borderK: 2, 68 | hovered: false, 69 | color: '#CCCCFF', 70 | textColor: 'white', 71 | hoveredColor: 'white', 72 | }), 73 | /** 74 | * calculate css based on hovered prop 75 | * better to use withProps as glamour uses class caching 76 | * BTW it's flow example 77 | */ 78 | withPropsOnChange( 79 | ['hovered', 'styles', 'color', 'hoveredColor', 'textColor'], 80 | ({ hovered, styles, color, textColor, hoveredColor }) => ({ 81 | styles: { 82 | ...styles, 83 | title: hovered 84 | ? css(styles.title, { color: hoveredColor }) 85 | : css(styles.title, { color: textColor }), 86 | 87 | border: hovered 88 | ? css(styles.border, { borderColor: hoveredColor }) 89 | : css(styles.border, { borderColor: color }), 90 | }, 91 | }) 92 | ), 93 | /** 94 | * calculate component dynamic style 95 | */ 96 | withProps(({ size, x, y, hovered }) => ({ 97 | componentDynamicStyle: { 98 | width: size, 99 | height: size, 100 | left: x - size / 2, 101 | top: y - size / 2, 102 | zIndex: hovered ? 1 : 0, 103 | }, 104 | })), 105 | /** 106 | * generate borders props 107 | */ 108 | withProps(({ borderCount, borderK }) => ({ 109 | borders: Array(borderCount).fill(0).map((_, index) => ({ 110 | id: index, 111 | style: { 112 | transform: `rotate(${borderK * Math.PI * index / borderCount}rad)`, 113 | borderRadius: 5, 114 | }, 115 | })), 116 | })) 117 | ) 118 | 119 | export default enhanceItem(item) 120 | -------------------------------------------------------------------------------- /types/flow-example/src/ItemsAnimator.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from 'react' 4 | import { css } from 'glamor' 5 | import { compose, defaultProps, withHandlers, withProps } from 'recompose' 6 | import Item from './Item' 7 | import { TransitionMotion, spring } from 'react-motion' 8 | // types 9 | import type { HOC } from 'recompose' 10 | import type { MousePosition } from './MouseDetector' 11 | 12 | type ItemT = { 13 | id: number, 14 | title: string, 15 | color?: string, 16 | } 17 | 18 | // Props type of enhanced component 19 | // It's the only props type we need to declare using supporting recompose enhancers 20 | type ItemsAnimatorProps = { 21 | items: Array<ItemT>, 22 | config?: { 23 | size: number, 24 | hoverSize: number, 25 | hoverRotation: number, 26 | spacing: number, 27 | }, 28 | mousePos: MousePosition, 29 | } 30 | 31 | // set existential * type for base component, 32 | // flow is smart enough to infer base component and enhancers props types 33 | const itemsAnimator = ({ styles, animStyles }) => 34 | <TransitionMotion styles={animStyles}> 35 | {( 36 | interpolated: Array<{ 37 | key: string, 38 | style: { x: number, y: number, size: number, borderK: number }, 39 | data: { ...$Exact<ItemT>, hovered: boolean }, 40 | }> 41 | ) => 42 | <div {...styles.component}> 43 | {interpolated.map(({ key, data, style }) => 44 | <Item 45 | key={key} 46 | color={data.color} 47 | title={data.title} 48 | size={style.size} 49 | hovered={data.hovered} 50 | x={style.x} 51 | y={style.y} 52 | borderK={style.borderK} 53 | /> 54 | )} 55 | </div>} 56 | </TransitionMotion> 57 | 58 | const enhanceItemsAnimator: HOC<*, ItemsAnimatorProps> = compose( 59 | /** 60 | * Defaults 61 | */ 62 | defaultProps({ 63 | styles: { 64 | component: css({ 65 | position: 'absolute', 66 | }), 67 | }, 68 | config: { 69 | size: 110, 70 | hoverSize: 150, 71 | hoverRotation: 2 * Math.PI, 72 | spacing: -10, 73 | }, 74 | springConfig: { 75 | stiffness: 170, 76 | damping: 4, 77 | precision: 0.001, 78 | }, 79 | }), 80 | /** 81 | * Function to calculate items positions size and hover 82 | * based on mouse position and previously hovered item 83 | */ 84 | withHandlers(() => { 85 | let hoveredItemId_ = -1 86 | 87 | return { 88 | getItemsViewProps: ({ mousePos, items, config }) => () => { 89 | if (items.length === 0) return [] 90 | 91 | const itemMaxWidth = config.size * Math.sqrt(2) 92 | const cIdx = (items.length - 1) / 2 93 | const itemsD = items 94 | .map((item, index) => ({ 95 | ...item, 96 | size: hoveredItemId_ === item.id ? config.hoverSize : config.size, 97 | x: (index - cIdx) * (itemMaxWidth + config.spacing), 98 | y: 0, 99 | })) 100 | .map(item => ({ 101 | ...item, 102 | x: 103 | hoveredItemId_ === item.id 104 | ? item.x + (mousePos.x - item.x) / 3 105 | : item.x, 106 | y: 107 | hoveredItemId_ === item.id 108 | ? item.y + (mousePos.y - item.y) / 3 109 | : item.y, 110 | })) 111 | .map(item => ({ 112 | ...item, 113 | distance: 114 | Math.sqrt( 115 | Math.pow(mousePos.x - item.x, 2) + Math.pow(mousePos.y, 2) 116 | ) / 117 | (item.size * Math.sqrt(2) / 2), 118 | })) 119 | const nearestItem = [...itemsD].sort( 120 | (a, b) => a.distance - b.distance 121 | )[0] 122 | 123 | if (nearestItem && nearestItem.distance < 1) { 124 | hoveredItemId_ = nearestItem.id 125 | // console.log('nearestItem', nearestItem); 126 | } else { 127 | hoveredItemId_ = -1 128 | } 129 | 130 | return itemsD.map(item => ({ 131 | ...item, 132 | hovered: item.id === hoveredItemId_, 133 | })) 134 | }, 135 | } 136 | }), 137 | /** 138 | * Recalculate items positions, size, hover 139 | */ 140 | withProps(({ getItemsViewProps }) => ({ 141 | items: getItemsViewProps(), 142 | })), 143 | /** 144 | * Prepare data for react-motion 145 | */ 146 | withProps(({ items, springConfig }) => ({ 147 | animStyles: items.map(item => ({ 148 | key: `${item.id}`, 149 | data: item, 150 | style: { 151 | x: spring(item.x, springConfig), 152 | y: spring(item.y, springConfig), 153 | size: spring(item.size, springConfig), 154 | borderK: spring(item.hovered ? 3 : 2, springConfig), 155 | }, 156 | })), 157 | })) 158 | ) 159 | 160 | export default enhanceItemsAnimator(itemsAnimator) 161 | -------------------------------------------------------------------------------- /types/flow-example/src/MouseDetector.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { 4 | compose, 5 | defaultProps, 6 | withHandlers, 7 | withProps, 8 | withStateHandlers, 9 | } from 'recompose' 10 | import { css } from 'glamor' 11 | import type { HOC } from 'recompose' 12 | 13 | // shared type 14 | export type MousePosition = { x: number, y: number } 15 | 16 | // Props type of enhanced component 17 | // It's the only props type we need to declare using supporting recompose enhancers 18 | type EnhancedMouseDetectorProps = { 19 | styles?: { component: Object }, 20 | children: (mousePos: MousePosition) => ?React$Element<any>, 21 | } 22 | 23 | // base component 24 | const mouseDetector = ({ styles, registerChild, children, mousePos }) => 25 | <div ref={registerChild} {...styles.component}> 26 | {children(mousePos)} 27 | </div> 28 | 29 | // set existential * type for base component i.e. mouseDetector, 30 | // flow is smart enough to infer base component and enhancers props types 31 | const enhanceMouseDetector: HOC<*, EnhancedMouseDetectorProps> = compose( 32 | /** 33 | * styles can be overwritten by parent component 34 | */ 35 | defaultProps({ 36 | styles: { 37 | component: css({ 38 | backgroundColor: 'red', 39 | position: 'relative', 40 | width: 0, 41 | height: 0, 42 | }), 43 | }, 44 | }), 45 | /** 46 | * Component state and state handlers needed to calculate mouse position 47 | * relative to current component 48 | */ 49 | withStateHandlers( 50 | // flow is smart enough to detect state type 51 | { mousePos: { x: Infinity, y: Infinity }, offset: { x: 0, y: 0 } }, 52 | { 53 | // flow infers state and props types but 54 | // we need to declare type for handler argument i.e. x,y here 55 | // otherwise type inference will not work for following enhancers 56 | setMousePosition: (state, props) => (x: number, y: number) => ({ 57 | mousePos: { x, y }, 58 | }), 59 | // the same as above, we need to declare arg type 60 | setInitialOffsetYAndScrollY: () => (x: number, y: number) => ({ 61 | offset: { 62 | x, 63 | y, 64 | }, 65 | }), 66 | } 67 | ), 68 | /** 69 | * withHandlers hack to intercept document events 70 | */ 71 | withHandlers(() => { 72 | let resizeHandler_ 73 | let mouseMoveHandler_ 74 | 75 | return { 76 | // hovering on state updater functions gives you a type like 77 | // Void<SomeFunctionType>, Void type helper transforms function result type to void 78 | // See libdef and tests, I haven't found more prettier solution 79 | registerChild: ({ setInitialOffsetYAndScrollY, setMousePosition }) => ( 80 | ref: ?HTMLDivElement 81 | ) => { 82 | // I have no idea why window has any type so this is not covered 83 | const window: Node = document.defaultView 84 | 85 | if (ref) { 86 | // like ComponentDidMount if ref is not null 87 | resizeHandler_ = () => { 88 | if (!ref) return 89 | const refRect = ref.getBoundingClientRect() 90 | 91 | const docRect = 92 | document.documentElement && 93 | document.documentElement.getBoundingClientRect() 94 | 95 | if (docRect) { 96 | // documentElement can be null so flow asks to check; 97 | const offsetX = refRect.left - docRect.left 98 | const offsetY = refRect.top - docRect.top 99 | 100 | setInitialOffsetYAndScrollY(offsetX, offsetY) 101 | } 102 | } 103 | 104 | // call initially to update state 105 | resizeHandler_() 106 | 107 | mouseMoveHandler_ = (evt: MouseEvent) => 108 | setMousePosition(evt.pageX, evt.pageY) 109 | 110 | window.addEventListener('resize', resizeHandler_) 111 | document.addEventListener('mousemove', mouseMoveHandler_) 112 | } else { 113 | // like ComponentWillUnmount if ref is null 114 | 115 | if (resizeHandler_) 116 | window.removeEventListener('resize', resizeHandler_) 117 | if (mouseMoveHandler_) 118 | document.removeEventListener('mousemove', mouseMoveHandler_) 119 | 120 | resizeHandler_ = undefined 121 | mouseMoveHandler_ = undefined 122 | } 123 | }, 124 | } 125 | }), 126 | /** 127 | * convert mouse coordinates into local 128 | */ 129 | withProps(({ mousePos, offset }) => ({ 130 | mousePos: { 131 | x: mousePos.x - offset.x, 132 | y: mousePos.y - offset.y, 133 | }, 134 | })) 135 | ) 136 | 137 | export default enhanceMouseDetector(mouseDetector) 138 | -------------------------------------------------------------------------------- /types/flow-example/src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import App from './App' 5 | import { css } from 'glamor' 6 | import 'glamor-reset' 7 | 8 | css.insert(` 9 | body, html { 10 | height: 100vh; 11 | font-size: 16px; 12 | color: #666; 13 | -webkit-font-smoothing: antialiased; 14 | background-color: indigo; 15 | } 16 | `) 17 | 18 | css.insert(` 19 | * { 20 | min-width: 0; 21 | min-height: 0; 22 | box-sizing: border-box; 23 | } 24 | `) 25 | 26 | css.insert(` 27 | #root { 28 | min-height: 100%; /* not 100 vh because of mobile chrome */ 29 | display: flex; 30 | } 31 | `) 32 | 33 | const mountNode = document.getElementById('root') 34 | ReactDOM.render(<App />, mountNode) 35 | 36 | if (module.hot) { 37 | ;((module.hot: any): { 38 | accept: (a: string, b: () => void) => void, 39 | }).accept('./App', () => { 40 | const NextApp = require('./App').default // eslint-disable-line 41 | ReactDOM.render(<NextApp />, mountNode) 42 | }) 43 | } 44 | --------------------------------------------------------------------------------