-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 94f90ea
Showing
4 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "react-pacomo", | ||
"version": "0.3.0", | ||
"description": "Provide structure to your component's CSS.", | ||
"main": "lib/pacomo.js", | ||
"scripts": { | ||
"compile": "babel --stage 0 -d lib/ src/", | ||
"prepublish": "npm run compile", | ||
"test": "npm run compile && mocha --compilers js:babel/register" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/jamesknelson/react-pacomo.git" | ||
}, | ||
"keywords": [ | ||
"react", | ||
"component", | ||
"decorator", | ||
"decorators", | ||
"classes", | ||
"props", | ||
"pacomo" | ||
], | ||
"author": "James K Nelson <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/jamesknelson/react-pacomo/issues" | ||
}, | ||
"homepage": "https://github.com/jamesknelson/react-pacomo#readme", | ||
"dependencies": { | ||
"classnames": "^2.1.3", | ||
"invariant": "^2.1.0", | ||
"react": "^0.14.0-beta3" | ||
}, | ||
"devDependencies": { | ||
"babel": "^5.8.23", | ||
"mocha": "^2.3.0", | ||
"react-addons-test-utils": "^0.14.0-beta3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import classNames from 'classnames' | ||
import invariant from 'invariant' | ||
import {PropTypes, cloneElement} from 'react' | ||
|
||
|
||
export function createDecorator(prefix) { | ||
return function decorator(component) { | ||
const componentName = component.displayName || component.name | ||
|
||
invariant( | ||
!component.prototype.pacomo, | ||
"pacomo must be applied to a class with no `pacomo` property" | ||
) | ||
|
||
const decoratedComponent = class extends component { | ||
pacomo(...args) { | ||
return ( | ||
classNames(...args) | ||
.split(/\s+/) | ||
.filter(name => name !== "") | ||
.map(name => `${prefix}-${componentName}-${name}`) | ||
.join(' ') | ||
) | ||
} | ||
|
||
render() { | ||
const result = super.render() | ||
return cloneElement( | ||
result, | ||
{className: `${prefix}-${componentName} ${result.props.className || ''} ${this.props.className || ''}`} | ||
) | ||
} | ||
} | ||
|
||
// Add `className` propType, if none exists | ||
if (!decoratedComponent.propTypes) { | ||
decoratedComponent.propTypes = {} | ||
} | ||
if (!decoratedComponent.propTypes.className) { | ||
decoratedComponent.propTypes = Object.assign( | ||
{className: PropTypes.string}, | ||
decoratedComponent.propTypes | ||
) | ||
} | ||
|
||
return decoratedComponent | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import {createDecorator} from './lib/pacomo' | ||
import {Component, DOM, PropTypes, createElement} from 'react' | ||
import TestUtils from 'react-addons-test-utils' | ||
import assert from 'assert' | ||
|
||
|
||
/* | ||
* Util | ||
*/ | ||
|
||
function renderComponent(component, props) { | ||
const shallowRenderer = TestUtils.createRenderer() | ||
shallowRenderer.render(createElement(component, props)) | ||
return shallowRenderer.getRenderOutput() | ||
} | ||
|
||
|
||
/* | ||
* Fixtures | ||
*/ | ||
|
||
class FullFeaturedComponent extends Component { | ||
render() { | ||
const className = this.pacomo({active: this.props.active}) | ||
return createElement(DOM.div, {className}) | ||
} | ||
} | ||
FullFeaturedComponent.propTypes = { | ||
className: PropTypes.string.isRequired, | ||
active: PropTypes.bool, | ||
} | ||
FullFeaturedComponent.defaultProps = { | ||
className: "defaultClassName", | ||
active: true, | ||
} | ||
|
||
|
||
class BareComponent extends Component { | ||
render() { | ||
return createElement(DOM.div) | ||
} | ||
} | ||
|
||
|
||
/* | ||
* Tests | ||
*/ | ||
|
||
|
||
describe('decorator', () => { | ||
it("should pass through propTypes from the original class", () => { | ||
const decoratedClass = createDecorator('prefix')(FullFeaturedComponent) | ||
|
||
assert.equal( | ||
decoratedClass.propTypes.active, | ||
PropTypes.bool, | ||
"className propType from original class is passed through" | ||
) | ||
|
||
assert.equal( | ||
decoratedClass.propTypes.className, | ||
PropTypes.string.isRequired, | ||
"other propTypes from origina class are passed through" | ||
) | ||
}) | ||
|
||
it("should add a className propType if one doesn't already exist", () => { | ||
const decoratedClass = createDecorator('prefix')(BareComponent) | ||
|
||
assert.equal( | ||
decoratedClass.propTypes.className, | ||
PropTypes.string, | ||
"className propType exists" | ||
) | ||
}) | ||
|
||
it ("cannot be applied twice", () => { | ||
let error | ||
|
||
try { | ||
const decorator = createDecorator('prefix') | ||
decorator(decorator(BareComponent)) | ||
} | ||
catch (ex) { | ||
error = ex | ||
} | ||
|
||
assert( | ||
!!error, | ||
"an error is thrown when a decorator is applied twice" | ||
) | ||
}) | ||
}) | ||
|
||
|
||
describe('#pacomo', () => { | ||
it("accepts an object, producing the correct result", () => { | ||
const pacomo = createDecorator('prefix')(FullFeaturedComponent).prototype.pacomo | ||
|
||
assert.equal( | ||
pacomo({ | ||
active: true, | ||
inactive: false, | ||
}), | ||
'prefix-FullFeaturedComponent-active', | ||
"`pacomo` produces the correct string when an object is passed in" | ||
) | ||
}) | ||
|
||
it("accepts a string, producing the correct result", () => { | ||
const pacomo = createDecorator('prefix')(BareComponent).prototype.pacomo | ||
|
||
assert.equal( | ||
pacomo('test'), | ||
'prefix-BareComponent-test', | ||
"`pacomo` produces the correct string when a string is passed in" | ||
) | ||
}) | ||
}) | ||
|
||
|
||
describe('#render', () => { | ||
it("appends passed in `className` to that returned by `super.render` and prefixes the component class name", () => { | ||
const rendered = renderComponent( | ||
createDecorator('prefix')(FullFeaturedComponent), | ||
{className: 'passedIn'} | ||
) | ||
|
||
assert.equal( | ||
rendered.props.className, | ||
'prefix-FullFeaturedComponent prefix-FullFeaturedComponent-active passedIn' | ||
) | ||
}) | ||
|
||
it("repects the value of the original class's `defaultProps.className`", () => { | ||
const rendered = renderComponent(createDecorator('prefix')(FullFeaturedComponent)) | ||
|
||
assert.equal( | ||
rendered.props.className, | ||
'prefix-FullFeaturedComponent prefix-FullFeaturedComponent-active defaultClassName' | ||
) | ||
}) | ||
|
||
it("works correctly when `super.render` does not return a `className`", () => { | ||
const rendered = renderComponent(createDecorator('prefix')(BareComponent)) | ||
|
||
assert.equal( | ||
rendered.props.className.trim(), | ||
'prefix-BareComponent' | ||
) | ||
}) | ||
}) |