Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesknelson committed Sep 1, 2015
0 parents commit 94f90ea
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
40 changes: 40 additions & 0 deletions package.json
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"
}
}
48 changes: 48 additions & 0 deletions src/pacomo.js
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
}
}
152 changes: 152 additions & 0 deletions test.js
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'
)
})
})

0 comments on commit 94f90ea

Please sign in to comment.