├── babel.config.js ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── SECURITY.md ├── CHANGELOG.md ├── LICENSE ├── .gitignore ├── .npmignore ├── package.json ├── index.js ├── README.md ├── CONTRIBUTING.md └── test └── index.test.js /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | '@babel/preset-react' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'godaddy-react', 3 | settings: { 4 | react: { 5 | version: 'detect' 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Summary 7 | 8 | 11 | 12 | ## Changelog 13 | 14 | 18 | 19 | ## Test Plan 20 | 21 | 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | We take security very seriously at GoDaddy. We appreciate your efforts to 4 | responsibly disclose your findings, and will make every effort to acknowledge 5 | your contributions. 6 | 7 | ## Where should I report security issues? 8 | 9 | In order to give the community time to respond and upgrade, we strongly urge you 10 | report all security issues privately. 11 | 12 | To report a security issue in one of our Open Source projects email us directly 13 | at **oss@godaddy.com** and include the word "SECURITY" in the subject line. 14 | 15 | This mail is delivered to our Open Source Security team. 16 | 17 | After the initial reply to your report, the team will keep you informed of the 18 | progress being made towards a fix and announcement, and may ask for additional 19 | information or guidance. 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x, 20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm test 27 | - run: npm run coverage 28 | if: ${{ matrix.node-version == '20.x' }} # only report coverage on latest Node 29 | - name: Coveralls 30 | if: ${{ matrix.node-version == '20.x' }} # only report coverage on latest Node 31 | uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `addhoc` 2 | 3 | ## 2.1.0 4 | 5 | - [dist] Relax peer deps for React 18 6 | 7 | ## 2.0.0 8 | 9 | - [dist] Drop React 16 support 10 | - [dist] Update dependencies 11 | 12 | ## 1.3.2 13 | 14 | - [dist] Update dependencies 15 | 16 | ## 1.3.1 17 | 18 | - [dist] Update dependencies 19 | 20 | ## 1.3.0 21 | 22 | - [dist] Use GitHub Actions for CI/CD 23 | - [fix] Fix typings in JSDoc 24 | - [dist] Drop standalone typings file as JSDoc types should be sufficient 25 | 26 | ## 1.2.1 27 | 28 | - [dist] Update dependencies 29 | 30 | ## 1.2.0 31 | 32 | - [feat] Add TypeScript typings (thanks @shawnkoon!) 33 | 34 | ## 1.1.2 35 | 36 | - [dist] Update dependencies 37 | 38 | ## 1.1.1 39 | 40 | - [dist] Update dependencies 41 | 42 | ## 1.1.0 43 | 44 | - [fix] Added missing displayName assignment 45 | 46 | ## 1.0.2 47 | 48 | - [dist] Bump to latest babel & mocha 49 | 50 | ## 1.0.1 51 | 52 | - [dist] Bump `eslint-config-godaddy-react` 53 | 54 | ## 1.0.0 55 | 56 | - Initial release 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 GoDaddy Operating Company, LLC. 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # built files 64 | lib 65 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Test files 64 | test/ 65 | 66 | # explicitly leave lib in the package 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "addhoc", 3 | "version": "2.1.0", 4 | "description": "Handy little helper to create proper HOC functions complete with hoisted statics and forwarded refs", 5 | "main": "./lib/index", 6 | "browser": "./lib/index", 7 | "module": "./index", 8 | "react-native": "./index", 9 | "scripts": { 10 | "build": "babel -d lib index.js", 11 | "prepublishOnly": "npm run build", 12 | "lint": "eslint --fix *.js", 13 | "test": "nyc mocha --require setup-env test/*.test.js", 14 | "posttest": "npm run lint", 15 | "coverage": "nyc report --reporter=lcov" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+ssh://git@github.com/godaddy/addhoc.git" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "hoc", 24 | "component", 25 | "ref" 26 | ], 27 | "author": "Jonathan Keslin ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/godaddy/addhoc/issues" 31 | }, 32 | "homepage": "https://github.com/godaddy/addhoc#readme", 33 | "devDependencies": { 34 | "@babel/cli": "^7.16.8", 35 | "@babel/core": "^7.16.12", 36 | "@babel/preset-env": "^7.16.11", 37 | "@babel/preset-react": "^7.16.7", 38 | "@babel/register": "^7.16.9", 39 | "@types/react": "^17.0.38", 40 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.6", 41 | "assume": "^2.3.0", 42 | "babel-eslint": "^10.1.0", 43 | "enzyme": "^3.11.0", 44 | "eslint": "^8.7.0", 45 | "eslint-config-godaddy-react": "^8.0.0", 46 | "eslint-plugin-json": "^3.1.0", 47 | "eslint-plugin-jsx-a11y": "^6.5.1", 48 | "eslint-plugin-mocha": "^10.0.3", 49 | "eslint-plugin-react": "^7.28.0", 50 | "jsdom": "^19.0.0", 51 | "mocha": "^9.2.0", 52 | "nyc": "^15.1.0", 53 | "prop-types": "^15.8.1", 54 | "react": "^17.0.2", 55 | "react-dom": "^17.0.2", 56 | "setup-env": "^1.2.4" 57 | }, 58 | "dependencies": { 59 | "hoist-non-react-statics": "^3.3.2" 60 | }, 61 | "peerDependencies": { 62 | "react": "17.x || 18.x" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import hoistNonReactStatics from 'hoist-non-react-statics'; 3 | 4 | /** 5 | * @typedef {Function} withHOC 6 | * Higher-Order Component function to provide some functionality. 7 | * 8 | * @param {React.Component} WrappedComponent The component to be augmented with additional functionality 9 | * @returns {React.Component} A wrapped component class that provides additional functionality 10 | */ 11 | /** 12 | * @callback getWrappedComponentFn 13 | * @param {Object} extraProps A set of extra props, if any, to add to the wrapped component 14 | * @returns {React.ReactElement} The component to wrap with your HOC 15 | */ 16 | /** 17 | * @callback renderFn 18 | * @param {getWrappedComponentFn} getWrappedComponent A function to get the component being wrapped 19 | * @param {...any} [extraHOCArgs] Any additional arguments that you passed into addhoc will be passed through 20 | * @returns {React.ReactElement} A React component tree that contains the wrapped component 21 | */ 22 | /** 23 | * Handy little helper to create proper HOC functions complete with hoisted statics and forwarded refs 24 | * 25 | * @param {renderFn} renderFn A function that renders your HOC with the wrapped component inside -- see examples 26 | * @param {String} [name] Optional name of the HOC, used to augment the displayName on the wrapped component 27 | * @param {...any} [extraHOCArgs] Any additional arguments that you want to be passed through to your render function 28 | * @returns {withHOC} A higher order component function that accepts a component and returns it wrapped in your HOC 29 | * @public 30 | */ 31 | export default function addhoc(renderFn, name = 'WithHOC', ...extraHOCArgs) { 32 | return function withHOC(WrappedComponent) { 33 | function WithHOC(props) { 34 | const { forwardedRef, ...rest } = props; 35 | 36 | return renderFn(extraProps => { 37 | return ( 38 | 42 | ); 43 | }, ...extraHOCArgs); 44 | } 45 | 46 | // Wrap display name per 47 | // https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging 48 | WithHOC.displayName = `${name}(${getDisplayName(WrappedComponent)})`; 49 | 50 | // Copy over non-react statics per 51 | // https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over 52 | hoistNonReactStatics(WithHOC, WrappedComponent); 53 | 54 | // Wrap namespaced component to forward refs per https://reactjs.org/docs/forwarding-refs.html 55 | const forwardRef = React.forwardRef((props, ref) => { 56 | return ; 57 | }); 58 | 59 | // Also hoist statics onto forward ref for convenience 60 | hoistNonReactStatics(forwardRef, WrappedComponent); 61 | 62 | // Wrap display name per 63 | // https://reactjs.org/docs/forwarding-refs.html#displaying-a-custom-name-in-devtools 64 | forwardRef.displayName = `ForwardRef(${name}/${getDisplayName(WrappedComponent)})`; 65 | 66 | return forwardRef; 67 | }; 68 | } 69 | 70 | /** 71 | * Gets display name of a given component 72 | * 73 | * @param {React.ComponentType} WrappedComponent The component to retrieve a display name for 74 | * @returns {String} The display name of the given component, or `Component` by default 75 | * @private 76 | */ 77 | function getDisplayName(WrappedComponent) { 78 | return WrappedComponent.displayName || WrappedComponent.name || 'Component'; 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # addhoc 2 | 3 | Handy little helper to create proper HOC functions complete with hoisted statics and forwarded refs 4 | 5 | ## Motivation 6 | 7 | As defined in the [React documentation], a Higher Order Component, or HOC, is a function that returns a React component 8 | that wraps a specified child component and often provides augmented functionality. Implementing HOCs can be hard, 9 | especially when considering hoisting statics, managing `ref` forwarding, and handling display name. `addhoc` aims to 10 | handle these challenges for you. 11 | 12 | ## Benefits 13 | 14 | `addhoc` creates HOC functions that automatically: 15 | 16 | - [pass through unrelated `props`](https://reactjs.org/docs/higher-order-components.html#convention-pass-unrelated-props-through-to-the-wrapped-component) 17 | - [wrap the display name for easy debugging](https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging) 18 | - [hoist non-React statics](https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over) 19 | - [forward `refs`](https://reactjs.org/docs/higher-order-components.html#refs-arent-passed-through) 20 | 21 | ## Installation 22 | 23 | ```bash 24 | npm install addhoc 25 | ``` 26 | 27 | ### API 28 | 29 | ```ts 30 | /**** Public API ****/ 31 | // This is the main exported entrypoint 32 | addhoc(renderFn: Function, [name: String = 'WithHOC'], [...extraArgs]): Function 33 | 34 | /**** Signatures, not exported API ****/ 35 | // This is the signature of the renderFn parameter to addhoc() 36 | renderFn(getWrappedComponent: Function, [...extraArgs]): React.Component 37 | 38 | // This is the signature of the getWrappedComponent parameter to renderFn() 39 | getWrappedComponent([additionalProps: Object]): React.Component 40 | ``` 41 | 42 | ## Usage 43 | 44 | `addhoc` is a function that returns a HOC function. To construct your HOC, you simply pass a callback that acts as the 45 | render function of your top-level component. Your callback is provided a function parameter that returns the wrapped 46 | child that's initially provided to the HOC. You can call that callback with an object of `props` to add to the wrapped 47 | component. 48 | 49 | ### Example 1: Adding a prop 50 | 51 | ```jsx 52 | import addhoc from 'addhoc'; 53 | import MyComponent from './my-component'; 54 | 55 | const withFooProp = addhoc(getWrappedComponent => getWrappedComponent({ foo: true }), 'WithFooProp'); 56 | const MyComponentWithFoo = withFooProp(MyComponent); 57 | // Rendering a MyComponentWithFoo will create a MyComponent with prop foo = true 58 | ``` 59 | 60 | ### Example 2: Wrapping in another component 61 | 62 | ```jsx 63 | import React from 'react'; 64 | import addhoc from 'addhoc'; 65 | import MyComponent from './my-component'; 66 | 67 | const withDiv = addhoc(getWrappedComponent => 68 |
69 | { getWrappedComponent() } 70 |
, 'WithDiv'); 71 | const MyComponentWithDiv = withDiv(MyComponent); 72 | // Rendering a MyComponentWithDiv will render a div that wraps a MyComponent 73 | ``` 74 | 75 | ### Example 3: React 16 Context consumer 76 | 77 | ```jsx 78 | import React from 'react'; 79 | import addhoc from 'addhoc'; 80 | import MyComponent from './my-component'; 81 | 82 | const MyContext = React.createContext('DefaultValue'); 83 | const withMyContext = addhoc(getWrappedComponent => 84 | 85 | { value => getWrappedComponent({ value }) } 86 | , 'WithMyContext'); 87 | const MyComponentWithMyContext = withMyContext(MyComponent); 88 | 89 | // ... 90 | render() { 91 | return 92 | 93 | 94 | } 95 | 96 | // Now, the MyComponentWithMyContext automatically gets a prop called `value` that gets the context value passed in from 97 | // the context. 98 | ``` 99 | 100 | ### Example 4: Passing through configuration 101 | 102 | Sometimes, you want to set some values as part of assembling the HOC and have those available in your render function. 103 | You can pass arbitrary parameters after the `name` param to `addhoc` and they'll be passed through as additional 104 | parameters to your render function: 105 | 106 | ```jsx 107 | import addhoc from 'addhoc'; 108 | import MyComponent from './my-component'; 109 | 110 | const withFooProp = addhoc((getWrappedComponent, extra) => getWrappedComponent({ foo: extra }), 'WithFoo', 'EXTRA'); 111 | const MyComponentWithFoo = withFooProp(MyComponent); 112 | // Rendering a MyComponentWithFoo will get a `foo` prop with value `EXTRA` 113 | ``` 114 | 115 | ## Testing 116 | 117 | ```bash 118 | npm test 119 | ``` 120 | 121 | [React documentation]: https://reactjs.org/docs/higher-order-components.html 122 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Everyone is welcome to contribute to GoDaddy's Open Source Software. Contributing doesn’t just mean 4 | submitting pull requests. To get involved you can report or triage bugs and participate in 5 | discussions on the evolution of each project. 6 | 7 | No matter how you want to get involved, we ask that you first learn what’s expected of anyone who 8 | participates in the project by reading the Contribution Guidelines. 9 | 10 | ## Answering Questions 11 | 12 | One of the most important and immediate ways you can support this project is to answer questions on [Github][issues]. 13 | Whether you’re helping a newcomer understand a feature or troubleshooting an edge case with a 14 | seasoned developer, your knowledge and experience with JS can go a long way to help others. 15 | 16 | ## Reporting Bugs 17 | 18 | **Do not report potential security vulnerabilities here. Refer to [SECURITY.md](./SECURITY.md) for more 19 | details about the process of reporting security vulnerabilities.** 20 | 21 | Before submitting a ticket, please be sure to have a simple replication of the behavior. 22 | If the issue is isolated to one of the dependencies of this project. Please create a Github issue in that project. 23 | All dependencies are open source software and can be easily found through [npm]. 24 | 25 | Submit a ticket for your issue, assuming one does not already exist: 26 | - Create it on our [Issue Tracker][issues] 27 | - Clearly describe the issue by following the template layout 28 | - Make sure to include steps to reproduce the bug. 29 | - A reproducible (unit) test could be helpful in solving the bug. 30 | - Describe the environment that (re)produced the problem. 31 | 32 | > For a bug to be actionable, it needs to be reproducible. If you or contributors can’t reproduce the bug, 33 | > try to figure out why. Please take care to stay involved in discussions around solving the problem. 34 | 35 | ## Triaging bugs or contributing code 36 | 37 | If you're triaging a bug, try to reduce it. Once a bug can be reproduced, reduce it to the smallest amount of 38 | code possible. Reasoning about a sample or unit test that reproduces a bug in just a few lines of code is 39 | easier than reasoning about a longer sample. 40 | 41 | From a practical perspective, contributions are as simple as: 42 | - Forking the repository on GitHub. 43 | - Making changes to your forked repository. 44 | - When committing, reference your issue (if present) and include a note about the fix. 45 | - If possible, and if applicable, please also add/update unit tests for your changes. 46 | - Push the changes to your fork and submit a pull request to the 'master' branch of the projects' repository. 47 | 48 | If you are interested in making a large change and feel unsure about its overall effect, 49 | please make sure to first discuss the change and reach a consensus with core contributors. 50 | Then ask about the best way to go about making the change. 51 | 52 | ## Code Review 53 | 54 | Any open source project relies heavily on code review to improve software quality: 55 | 56 | > All significant changes, by all developers, must be reviewed before they are committed to the repository. 57 | > Code reviews are conducted on GitHub through comments on pull requests or commits. 58 | > The developer responsible for a code change is also responsible for making all necessary review-related changes. 59 | 60 | Sometimes code reviews will take longer than you would hope for, especially for larger features. 61 | Here are some accepted ways to speed up review times for your patches: 62 | 63 | - Review other people’s changes. If you help out, everybody will be more willing to do the same for you. 64 | Goodwill is our currency. 65 | - Split your change into multiple smaller changes. The smaller your change, the higher the probability that 66 | somebody will take a quick look at it. 67 | - Remember that you’re asking for valuable time from other professional developers. 68 | 69 | **Note that anyone is welcome to review and give feedback on a change, but only people with commit access 70 | to the repository can approve it.** 71 | 72 | ## Attribution of Changes 73 | 74 | When contributors submit a change to this project, after that change is approved, 75 | other developers with commit access may commit it for the author. When doing so, 76 | it is important to retain correct attribution of the contribution. Generally speaking, 77 | Git handles attribution automatically. 78 | 79 | ## Code Documentation 80 | 81 | Ensure that every function in `addhoc` is documented and follows the standards set by [JSDoc]. Finally, 82 | please stick to the code style as defined by the [Godaddy JS styleguide][style]. 83 | 84 | # Additional Resources 85 | 86 | - [General GitHub Documentation](https://help.github.com/) 87 | - [GitHub Pull Request documentation](https://help.github.com/send-pull-requests/) 88 | - [JSDoc] 89 | 90 | [issues]: https://github.com/godaddy/addhoc/issues 91 | [JSDoc]: http://usejsdoc.org/ 92 | [npm]: http://npmjs.org/ 93 | [style]: https://github.com/godaddy/javascript/#godaddy-style 94 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import assume from 'assume'; 4 | import addhoc from '../index'; 5 | import { mount } from 'enzyme'; 6 | 7 | describe('addhoc', function () { 8 | it('is a function', function () { 9 | assume(addhoc).is.a('function'); 10 | }); 11 | 12 | let tree; 13 | afterEach(function () { 14 | if (tree) { 15 | tree.unmount(); 16 | tree = null; 17 | } 18 | }); 19 | 20 | it('can add a prop', function () { 21 | const TestComponent = () => ; 22 | const withFooProp = addhoc(getWrappedComponent => getWrappedComponent({ foo: true })); 23 | const TestComponentWithFoo = withFooProp(TestComponent); 24 | tree = mount(); 25 | const testComponent = tree.find('TestComponent'); 26 | assume(testComponent.prop('foo')).is.true(); 27 | }); 28 | 29 | it('can do simple wrapping', function () { 30 | const TestComponent = () => ; 31 | const withDiv = addhoc(getWrappedComponent => 32 |
33 | { getWrappedComponent() } 34 |
); 35 | const TestComponentWithDiv = withDiv(TestComponent); 36 | tree = mount(); 37 | const span = tree.find('span'); 38 | assume(span.exists()).is.true(); 39 | assume(span.closest('div').exists()).is.true(); 40 | }); 41 | 42 | it('hoists statics', function () { 43 | const TestComponent = () => ; 44 | TestComponent.myStatic = 'MOCK'; 45 | const withDiv = addhoc(getWrappedComponent => 46 |
47 | { getWrappedComponent() } 48 |
); 49 | const TestComponentWithDiv = withDiv(TestComponent); 50 | assume(TestComponentWithDiv.myStatic).is.a('string'); 51 | assume(TestComponentWithDiv.myStatic).equals('MOCK'); 52 | }); 53 | 54 | it('augments the displayName properly when no HOC name is set', function () { 55 | const TestComponent = () => ; 56 | const withDiv = addhoc(getWrappedComponent => 57 |
58 | { getWrappedComponent() } 59 |
); 60 | const TestComponentWithDiv = withDiv(TestComponent); 61 | tree = mount(); 62 | assume(tree.exists('WithHOC(TestComponent)')).is.true(); 63 | }); 64 | 65 | it('augments the displayName properly when a custom HOC name is set', function () { 66 | const TestComponent = () => ; 67 | const withDiv = addhoc(getWrappedComponent => 68 |
69 | { getWrappedComponent() } 70 |
, 'WithDiv'); 71 | const TestComponentWithDiv = withDiv(TestComponent); 72 | tree = mount(); 73 | assume(tree.exists('WithDiv(TestComponent)')).is.true(); 74 | }); 75 | 76 | it('augments the displayName of the returned Component when a custom HOC name is set', function () { 77 | const TestComponent = () =>
; 78 | const withDiv = addhoc(getWrappedComponent => 79 |
80 | { getWrappedComponent() } 81 |
, 'WithDiv'); 82 | 83 | const TestComponentWithDiv = withDiv(TestComponent); 84 | assume(TestComponentWithDiv.displayName).equals('ForwardRef(WithDiv/TestComponent)'); 85 | }); 86 | 87 | it('augments the displayName of the returned Component when no HOC name is set', function () { 88 | const TestComponent = () =>
; 89 | const withDiv = addhoc(getWrappedComponent => 90 |
91 | { getWrappedComponent() } 92 |
); 93 | 94 | const TestComponentWithDiv = withDiv(TestComponent); 95 | assume(TestComponentWithDiv.displayName).equals('ForwardRef(WithHOC/TestComponent)'); 96 | }); 97 | 98 | it('can create a HOC that uses the React 16 context API', function () { 99 | const TestComponent = props => { props.value }; 100 | TestComponent.propTypes = { 101 | value: PropTypes.string 102 | }; 103 | const TestContext = React.createContext('DEFAULT'); 104 | const withContext = addhoc(getWrappedComponent => 105 | 106 | { value => getWrappedComponent({ value }) } 107 | ); 108 | const TestComponentWithContext = withContext(TestComponent); 109 | tree = mount( 110 | 111 | 112 | 113 | 114 | 115 | ); 116 | 117 | const span = tree.find('span'); 118 | assume(span.text()).equals('PROVIDED'); 119 | }); 120 | 121 | it('forwards refs', function () { 122 | class TestComponent extends React.Component { 123 | // Used to test ref forwarding 124 | getMockData() { 125 | return 'MOCK_DATA'; 126 | } 127 | 128 | render() { 129 | return ( 130 | 131 | ); 132 | } 133 | } 134 | const withDiv = addhoc(getWrappedComponent => 135 |
136 | { getWrappedComponent() } 137 |
); 138 | const TestComponentWithDiv = withDiv(TestComponent); 139 | class TestRefComponent extends React.Component { 140 | constructor(...args) { 141 | // @ts-expect-error 142 | super(...args); 143 | this.componentRef = React.createRef(); 144 | this.state = { 145 | updated: false, 146 | data: null 147 | }; 148 | } 149 | 150 | componentDidMount() { 151 | this.setState({ 152 | updated: true, 153 | data: this.componentRef.current.getMockData() 154 | }); 155 | } 156 | 157 | render() { 158 | return ( 159 | 160 | 161 | { this.state.data } 162 | 163 | ); 164 | } 165 | } 166 | 167 | tree = mount( 168 | 169 | ); 170 | 171 | assume(tree.state('updated')).is.true(); 172 | assume(tree.find('.data').text()).equals('MOCK_DATA'); 173 | }); 174 | 175 | it('passes through extra args to the HOC renderFn', function () { 176 | const TestComponent = () => ; 177 | const withFooProp = addhoc((getWrappedComponent, extra) => getWrappedComponent({ foo: extra }), 'WithFoo', 'EXTRA'); 178 | const TestComponentWithFoo = withFooProp(TestComponent); 179 | tree = mount(); 180 | const testComponent = tree.find('TestComponent'); 181 | assume(testComponent.prop('foo')).equals('EXTRA'); 182 | }); 183 | }); 184 | --------------------------------------------------------------------------------