Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/components/themr.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>

static propTypes = {
composeTheme: PropTypes.oneOf([ COMPOSE_DEEPLY, COMPOSE_SOFTLY, DONT_COMPOSE ]),
theme: PropTypes.object
theme: PropTypes.object,
themeNamespace: PropTypes.string
}

static defaultProps = {
Expand All @@ -38,8 +39,19 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
return this.refs.wrappedInstance
}

getNamespacedTheme() {
const { themeNamespace, theme } = this.props
if (!themeNamespace) return theme
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
'themeNamespace prop should be used only with theme prop.')

return Object.keys(theme)
.filter(key => key.startsWith(themeNamespace))
.reduce((result, key) => ({ ...result, [removeNamespace(key, themeNamespace)]: theme[key] }), {})
}

getThemeNotComposed() {
if (this.props.theme) return this.props.theme
if (this.props.theme) return this.getNamespacedTheme()
if (localTheme) return localTheme
return this.getContextTheme()
}
Expand All @@ -52,8 +64,8 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>

getTheme() {
return this.props.composeTheme === COMPOSE_SOFTLY
? Object.assign({}, this.getContextTheme(), localTheme, this.props.theme)
: themeable(themeable(this.getContextTheme(), localTheme), this.props.theme)
? Object.assign({}, this.getContextTheme(), localTheme, this.getNamespacedTheme())
: themeable(themeable(this.getContextTheme(), localTheme), this.getNamespacedTheme())
}

render() {
Expand Down Expand Up @@ -101,3 +113,8 @@ function validateComposeOption(composeTheme) {
)
}
}

function removeNamespace(key, themeNamespace) {
const capitilized = key.substr(themeNamespace.length)
return capitilized.slice(0, 1).toLowerCase() + capitilized.slice(1)
}
41 changes: 41 additions & 0 deletions test/components/themr.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,45 @@ describe('Themr decorator function', () => {
expect(decorated.getWrappedInstance().someInstanceMethod()).toBe(someData)
expect(decorated.refs.wrappedInstance.someInstanceMethod()).toBe(someData)
})

it('should throw if themeNamespace passed without theme', () => {
const theme = { Container: { foo: 'foo_1234' } }

@themr('Container')
class Container extends Component {
render() {
return <Passthrough {...this.props} />
}
}

expect(() => TestUtils.renderIntoDocument(
<ProviderMock theme={theme}>
<Container themeNamespace="container"/>
</ProviderMock>
)).toThrow(/Invalid themeNamespace use in react-css-themr. themeNamespace prop should be used only with theme prop./)
})

it('when providing a themeNamespace prop composes a theme', () => {
const containerTheme = { foo: 'foo_123' }
const containerThemeLocal = { foo: 'foo_567' }
const containerThemeProps = { foo: 'foo_89', containerFoo: 'foo_000' }
const theme = { Container: containerTheme }

@themr('Container', containerThemeLocal)
class Container extends Component {
render() {
return <Passthrough {...this.props} />
}
}

const tree = TestUtils.renderIntoDocument(
<ProviderMock theme={theme}>
<Container theme={containerThemeProps} themeNamespace="container" />
</ProviderMock>
)

const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough)
const expectedTheme = { foo: 'foo_123 foo_567 foo_000' }
expect(stub.props.theme).toEqual(expectedTheme)
})
})