Skip to content

Commit

Permalink
Merge pull request #24 from helpscout/custom-emotion-instance
Browse files Browse the repository at this point in the history
Emotion: Custom instance to avoid global conflict
  • Loading branch information
ItsJonQ committed Jul 25, 2018
2 parents 51eb988 + 221e826 commit 35caecc
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 28 deletions.
50 changes: 31 additions & 19 deletions src/create-emotion-styled/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
import PropTypes from 'prop-types'
import type { ElementType } from 'react'
import type {ElementType} from 'react'
import typeof ReactType from 'react'
import type { CreateStyled, StyledOptions } from './utils'
import type {CreateStyled, StyledOptions} from './utils'
import {
themeChannel as channel,
testPickPropsOnComponent,
Expand All @@ -13,9 +13,9 @@ import {
setFrame,
} from './utils'
import FrameManager from './FrameManager'
import { getDocumentFromReactComponent } from '../utils'
import { channel as frameChannel } from '../FrameProvider'
import { channel as scopeChannel } from '../ScopeProvider'
import {getDocumentFromReactComponent} from '../utils'
import {channel as frameChannel} from '../FrameProvider'
import {channel as scopeChannel} from '../ScopeProvider'

const contextTypes = {
[channel]: PropTypes.object,
Expand All @@ -28,7 +28,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
if (process.env.NODE_ENV !== 'production') {
if (tag === undefined) {
throw new Error(
'You are trying to create a styled element with an undefined component.\nYou may have forgotten to import it.'
'You are trying to create a styled element with an undefined component.\nYou may have forgotten to import it.',
)
}
}
Expand Down Expand Up @@ -84,7 +84,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
}
}

class Styled extends view.Component<*, { theme: Object }> {
class Styled extends view.Component<*, {theme: Object}> {
unsubscribe: number
unsubscribeFrame: number
mergedProps: Object
Expand All @@ -95,6 +95,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
static __emotion_target: string
static __emotion_forwardProp: void | (string => boolean)
static withComponent: (ElementType, options?: StyledOptions) => any
// $FlowFixMe
state = {}
// Custom instance properties
emotion = emotion
Expand All @@ -103,15 +104,15 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
componentWillMount() {
if (this.context[channel] !== undefined) {
this.unsubscribe = this.context[channel].subscribe(
setTheme.bind(this)
setTheme.bind(this),
)
}
/**
* Extra channel for the Frame
*/
if (this.context[frameChannel] !== undefined) {
this.unsubscribeFrame = this.context[frameChannel].subscribe(
setFrame.bind(this)
setFrame.bind(this),
)
}
}
Expand All @@ -135,6 +136,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
* custom container.
*/
setEmotion() {
// $FlowFixMe
const frame = this.state.frame

if (!frame) return
Expand Down Expand Up @@ -166,7 +168,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
}
}
render() {
const { props, state } = this
const {props, state} = this
this.mergedProps = pickAssign(testAlwaysTrue, {}, props, {
theme: (state !== null && state.theme) || props.theme || {},
})
Expand All @@ -179,17 +181,27 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
if (staticClassName === undefined) {
className += this.emotion.getRegisteredStyles(
classInterpolations,
props.className
props.className,
)
} else {
className += `${props.className} `
}
}
if (staticClassName === undefined) {
className += this.emotion
/* Replaces emotion.css, with enhanced emotion.cssWithScope */
.cssWithScope(this.getScope())
.apply(this, styles.concat(classInterpolations))
/* Replaces emotion.css, with enhanced emotion.cssWithScope */
if (
this.emotion.hasOwnProperty('cssWithScope') &&
typeof this.emotion.cssWithScope === 'function'
) {
className += this.emotion
.cssWithScope(this.getScope())
.apply(this, styles.concat(classInterpolations))
} else {
className += this.emotion.css.apply(
this,
styles.concat(classInterpolations),
)
}
} else {
className += staticClassName
}
Expand All @@ -204,7 +216,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
pickAssign(shouldForwardProp, {}, props, {
className,
ref: props.innerRef,
})
}),
)
}
}
Expand Down Expand Up @@ -242,14 +254,14 @@ function createEmotionStyled(emotion: Object, view: ReactType) {

Styled.withComponent = (
nextTag: ElementType,
nextOptions?: StyledOptions
nextOptions?: StyledOptions,
) => {
return createStyled(
nextTag,
nextOptions !== undefined
? // $FlowFixMe
pickAssign(testAlwaysTrue, {}, options, nextOptions)
: options
: options,
)(...styles)
}

Expand All @@ -270,7 +282,7 @@ function createEmotionStyled(emotion: Object, view: ReactType) {
default: {
throw new Error(
`You're trying to use the styled shorthand without babel-plugin-this.` +
`\nPlease install and setup babel-plugin-emotion or use the function call syntax(\`styled('${property}')\` instead of \`styled.${property}\`)`
`\nPlease install and setup babel-plugin-emotion or use the function call syntax(\`styled('${property}')\` instead of \`styled.${property}\`)`,
)
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/create-emotion/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import createEmotion from '../index'

describe('createEmotion', () => {
afterEach(() => {
global.__SECRET_EMOTION__ = undefined
global.__SECRET_FANCY_EMOTION__ = undefined
})

test('Creats unique global instance of Emotion', () => {
const inst = createEmotion(global)

expect(global.__SECRET_FANCY_EMOTION__).toBe(inst)
})

test('Does not collide with stock instance of Emotion', () => {
const mockEmotion = {}
global.__SECRET_EMOTION__ = mockEmotion

const inst = createEmotion(global)

expect(global.__SECRET_EMOTION__).toBe(mockEmotion)
expect(global.__SECRET_FANCY_EMOTION__).toBe(inst)
})
})
9 changes: 5 additions & 4 deletions src/create-emotion/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type CreateStyles<ReturnValue> = (...args: Interpolations) => ReturnValue

export type Emotion = {
css: CreateStyles<string>,
cssWithScope: Function,
cx: (...classNames: Array<ClassNameArg>) => string,
flush: () => void,
getRegisteredStyles: (
Expand All @@ -62,11 +63,11 @@ type EmotionOptions = {
}

function createEmotion(
context: {__SECRET_EMOTION__?: Emotion},
context: {__SECRET_FANCY_EMOTION__?: Emotion},
options?: EmotionOptions,
): Emotion {
if (context.__SECRET_EMOTION__ !== undefined) {
return context.__SECRET_EMOTION__
if (context.__SECRET_FANCY_EMOTION__ !== undefined) {
return context.__SECRET_FANCY_EMOTION__
}
if (options === undefined) options = {}
let key = options.key || 'css'
Expand Down Expand Up @@ -399,7 +400,7 @@ function createEmotion(
sheet,
caches,
}
context.__SECRET_EMOTION__ = emotion
context.__SECRET_FANCY_EMOTION__ = emotion
return emotion
}

Expand Down
24 changes: 21 additions & 3 deletions src/styled/__tests__/styled.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { mount } from 'enzyme'
import {mount} from 'enzyme'
import styled from '../index'
import { getStyleProp, resetStyleTags } from '../../utils/testHelpers'
import {getStyleProp, resetStyleTags} from '../../utils/testHelpers'

describe('styled', () => {
afterEach(() => {
Expand Down Expand Up @@ -82,10 +82,28 @@ describe('styled', () => {
expect(getStyleProp(el, 'background')).toBe('yellow')
expect(getStyleProp(el, 'color')).not.toBe('red')

wrapper.setProps({ title: 'Clever' })
wrapper.setProps({title: 'Clever'})

expect(getStyleProp(el, 'background')).toBe('yellow')
expect(getStyleProp(el, 'color')).toBe('red')
})

test('Falls back to emotion.css if emotion.cssWithScope is unavailable', () => {
const spy = jest.fn()
const Compo = styled('span')`
background: yellow;
${props => props.title && 'color: red;'};
`

const wrapper = mount(<Compo />)
const el = wrapper.find('span').getNode()

wrapper.instance().emotion.cssWithScope = undefined
wrapper.instance().emotion.css = spy

wrapper.setProps({title: 'Clever'})

expect(spy).toHaveBeenCalled()
})
})
})
4 changes: 2 additions & 2 deletions src/utils/testHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const getStyleProp = (node: HTMLElement, prop: string = 'display') =>
* Resets the <head> tag to remove stray <style> tags.
*/
export const resetStyleTags = () => {
if (global.__SECRET_EMOTION__) {
global.__SECRET_EMOTION__.flush()
if (global.__SECRET_FANCY_EMOTION__) {
global.__SECRET_FANCY_EMOTION__.flush()
} else {
global.document.head.innerHTML = ''
}
Expand Down

0 comments on commit 35caecc

Please sign in to comment.