styled-components 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/CODE_OF_CONDUCT.md +1 -1
  3. package/README.md +12 -67
  4. package/dist/styled-components.es.js +273 -110
  5. package/dist/styled-components.js +325 -119
  6. package/dist/styled-components.min.js +2 -2
  7. package/lib/hoc/withTheme.js +13 -7
  8. package/lib/models/BrowserStyleSheet.js +11 -0
  9. package/lib/models/ComponentStyle.js +45 -2
  10. package/lib/models/InlineStyle.js +1 -1
  11. package/lib/models/ServerStyleSheet.js +33 -17
  12. package/lib/models/StyleSheet.js +9 -0
  13. package/lib/models/StyledComponent.js +82 -38
  14. package/lib/models/StyledNativeComponent.js +31 -15
  15. package/lib/models/ThemeProvider.js +44 -12
  16. package/lib/native/index.js +1 -1
  17. package/lib/test/utils.js +5 -2
  18. package/lib/utils/create-broadcast.js +34 -24
  19. package/lib/utils/domElements.js +1 -1
  20. package/lib/utils/flatten.js +4 -1
  21. package/lib/utils/generateAlphabeticName.js +1 -1
  22. package/lib/utils/nonce.js +10 -0
  23. package/lib/utils/once.js +17 -0
  24. package/package.json +10 -10
  25. package/src/hoc/withTheme.js +14 -7
  26. package/src/models/BrowserStyleSheet.js +8 -0
  27. package/src/models/ComponentStyle.js +42 -2
  28. package/src/models/InlineStyle.js +1 -1
  29. package/src/models/ServerStyleSheet.js +27 -12
  30. package/src/models/StyleSheet.js +9 -0
  31. package/src/models/StyledComponent.js +81 -26
  32. package/src/models/StyledNativeComponent.js +30 -10
  33. package/src/models/ThemeProvider.js +38 -9
  34. package/src/models/test/ThemeProvider.test.js +7 -8
  35. package/src/native/index.js +1 -1
  36. package/src/native/test/native.test.js +14 -0
  37. package/src/test/__snapshots__/ssr.test.js.snap +147 -0
  38. package/src/test/expanded-api.test.js +24 -0
  39. package/src/test/props.test.js +14 -3
  40. package/src/test/ssr.test.js +90 -123
  41. package/src/test/styles.test.js +52 -0
  42. package/src/test/utils.js +5 -2
  43. package/src/utils/create-broadcast.js +31 -17
  44. package/src/utils/domElements.js +1 -0
  45. package/src/utils/flatten.js +16 -6
  46. package/src/utils/generateAlphabeticName.js +1 -1
  47. package/src/utils/nonce.js +6 -0
  48. package/src/utils/once.js +12 -0
  49. package/typings/styled-components.d.ts +15 -21
  50. package/typings/tests/issue1068.tsx +226 -0
  51. package/typings/tests/main-test.tsx +1 -1
  52. package/typings/tests/string-tags-test.tsx +62 -0
  53. package/typings/tests/themed-tests/issue1068.tsx +226 -0
  54. package/typings/tests/themed-tests/mytheme-styled-components.tsx +1 -1
  55. package/typings/tests/themed-tests/with-theme-test.tsx +2 -1
  56. package/typings/tests/with-theme-test.tsx +17 -0
  57. package/lib/constructors/test/injectGlobal.test.js +0 -63
  58. package/lib/constructors/test/keyframes.test.js +0 -48
  59. package/lib/constructors/test/styled.test.js +0 -19
  60. package/lib/models/AbstractStyledComponent.js +0 -43
  61. package/lib/models/test/ThemeProvider.test.js +0 -200
  62. package/lib/native/test/native.test.js +0 -290
  63. package/lib/no-parser/test/basic.test.js +0 -46
  64. package/lib/no-parser/test/flatten.test.js +0 -125
  65. package/lib/no-parser/test/keyframes.test.js +0 -45
  66. package/lib/primitives/test/primitives.test.js +0 -289
  67. package/lib/test/attrs.test.js +0 -158
  68. package/lib/test/basic.test.js +0 -267
  69. package/lib/test/css.test.js +0 -43
  70. package/lib/test/expanded-api.test.js +0 -90
  71. package/lib/test/extending.test.js +0 -198
  72. package/lib/test/overriding.test.js +0 -35
  73. package/lib/test/props.test.js +0 -38
  74. package/lib/test/rehydration.test.js +0 -306
  75. package/lib/test/ssr.test.js +0 -187
  76. package/lib/test/styles.test.js +0 -146
  77. package/lib/test/theme.test.js +0 -497
  78. package/lib/test/warnTooManyClasses.test.js +0 -71
  79. package/lib/utils/test/extractCompsFromCSS.test.js +0 -46
  80. package/lib/utils/test/flatten.test.js +0 -109
  81. package/lib/utils/test/generateAlphabeticName.test.js +0 -14
  82. package/lib/utils/test/interleave.test.js +0 -22
  83. package/lib/utils/test/validAttr.test.js +0 -560
  84. package/src/models/AbstractStyledComponent.js +0 -21
  85. package/typings/tags.d.ts +0 -137
@@ -1,5 +1,6 @@
1
1
  // @flow
2
- import { createElement } from 'react'
2
+ import { Component, createElement } from 'react'
3
+ import PropTypes from 'prop-types'
3
4
 
4
5
  import type { Theme } from './ThemeProvider'
5
6
 
@@ -8,11 +9,10 @@ import isStyledComponent from '../utils/isStyledComponent'
8
9
  import getComponentName from '../utils/getComponentName'
9
10
  import type { RuleSet, Target } from '../types'
10
11
 
11
- import { CHANNEL } from './ThemeProvider'
12
- import AbstractStyledComponent from './AbstractStyledComponent'
12
+ import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider'
13
13
 
14
14
  export default (constructWithOptions: Function, InlineStyle: Function) => {
15
- class BaseStyledNativeComponent extends AbstractStyledComponent {
15
+ class BaseStyledNativeComponent extends Component {
16
16
  static target: Target
17
17
  static styledComponentId: string
18
18
  static attrs: Object
@@ -25,6 +25,15 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
25
25
  generatedStyles: undefined,
26
26
  }
27
27
 
28
+ unsubscribeId: number = -1
29
+
30
+ unsubscribeFromContext() {
31
+ if (this.unsubscribeId !== -1) {
32
+ this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
33
+ }
34
+ }
35
+
36
+
28
37
  buildExecutionContext(theme: any, props: any) {
29
38
  const { attrs } = this.constructor
30
39
  const context = { ...props, theme }
@@ -53,20 +62,24 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
53
62
  // If there is a theme in the context, subscribe to the event emitter. This
54
63
  // is necessary due to pure components blocking context updates, this circumvents
55
64
  // that by updating when an event is emitted
56
- if (this.context[CHANNEL]) {
57
- const subscribe = this.context[CHANNEL]
58
- this.unsubscribe = subscribe(nextTheme => {
65
+ const styledContext = this.context[CHANNEL_NEXT]
66
+ if (styledContext !== undefined) {
67
+ const { subscribe } = styledContext
68
+ this.unsubscribeId = subscribe(nextTheme => {
59
69
  // This will be called once immediately
60
70
 
61
71
  // Props should take precedence over ThemeProvider, which should take precedence over
62
72
  // defaultProps, but React automatically puts defaultProps on props.
63
73
  const { defaultProps } = this.constructor
74
+ /* eslint-disable react/prop-types */
64
75
  const isDefaultTheme = defaultProps && this.props.theme === defaultProps.theme
65
76
  const theme = this.props.theme && !isDefaultTheme ? this.props.theme : nextTheme
77
+ /* eslint-enable */
66
78
  const generatedStyles = this.generateAndInjectStyles(theme, this.props)
67
79
  this.setState({ theme, generatedStyles })
68
80
  })
69
81
  } else {
82
+ // eslint-disable-next-line react/prop-types
70
83
  const theme = this.props.theme || {}
71
84
  const generatedStyles = this.generateAndInjectStyles(
72
85
  theme,
@@ -81,8 +94,10 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
81
94
  // Props should take precedence over ThemeProvider, which should take precedence over
82
95
  // defaultProps, but React automatically puts defaultProps on props.
83
96
  const { defaultProps } = this.constructor
97
+ /* eslint-disable react/prop-types */
84
98
  const isDefaultTheme = defaultProps && nextProps.theme === defaultProps.theme
85
99
  const theme = nextProps.theme && !isDefaultTheme ? nextProps.theme : oldState.theme
100
+ /* eslint-enable */
86
101
  const generatedStyles = this.generateAndInjectStyles(theme, nextProps)
87
102
 
88
103
  return { theme, generatedStyles }
@@ -90,9 +105,7 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
90
105
  }
91
106
 
92
107
  componentWillUnmount() {
93
- if (this.unsubscribe) {
94
- this.unsubscribe()
95
- }
108
+ this.unsubscribeFromContext()
96
109
  }
97
110
 
98
111
  setNativeProps(nativeProps: Object) {
@@ -112,6 +125,7 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
112
125
  }
113
126
 
114
127
  onRef = (node: any) => {
128
+ // eslint-disable-next-line react/prop-types
115
129
  const { innerRef } = this.props
116
130
  this.root = node
117
131
 
@@ -121,6 +135,7 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
121
135
  }
122
136
 
123
137
  render() {
138
+ // eslint-disable-next-line react/prop-types
124
139
  const { children, style } = this.props
125
140
  const { generatedStyles } = this.state
126
141
  const { target } = this.constructor
@@ -171,6 +186,11 @@ export default (constructWithOptions: Function, InlineStyle: Function) => {
171
186
  static attrs = attrs
172
187
  static inlineStyle = inlineStyle
173
188
 
189
+ static contextTypes = {
190
+ [CHANNEL]: PropTypes.func,
191
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
192
+ }
193
+
174
194
  // NOTE: This is so that isStyledComponent passes for the innerRef unwrapping
175
195
  static styledComponentId = 'StyledNativeComponent'
176
196
 
@@ -6,9 +6,17 @@ import isFunction from 'is-function'
6
6
  import isPlainObject from 'is-plain-object'
7
7
  import createBroadcast from '../utils/create-broadcast'
8
8
  import type { Broadcast } from '../utils/create-broadcast'
9
+ import once from '../utils/once'
9
10
 
10
11
  // NOTE: DO NOT CHANGE, changing this is a semver major change!
11
12
  export const CHANNEL = '__styled-components__'
13
+ export const CHANNEL_NEXT = `${CHANNEL}next__`
14
+
15
+ export const CONTEXT_CHANNEL_SHAPE = PropTypes.shape({
16
+ getTheme: PropTypes.func,
17
+ subscribe: PropTypes.func,
18
+ unsubscribe: PropTypes.func,
19
+ })
12
20
 
13
21
  export type Theme = {[key: string]: mixed}
14
22
  type ThemeProviderProps = {|
@@ -16,6 +24,11 @@ type ThemeProviderProps = {|
16
24
  theme: Theme | (outerTheme: Theme) => void,
17
25
  |}
18
26
 
27
+
28
+ const warnChannelDeprecated = once(() => {
29
+ // eslint-disable-next-line no-console
30
+ console.error(`Warning: Usage of \`context.${CHANNEL}\` as a function is deprecated. It will be replaced with the object on \`.context.${CHANNEL_NEXT}\` in a future version.`)
31
+ })
19
32
  /**
20
33
  * Provide a theme to an entire react component tree via context and event listeners (have to do
21
34
  * both context and event emitter as pure components block context updates)
@@ -23,9 +36,10 @@ type ThemeProviderProps = {|
23
36
  class ThemeProvider extends Component {
24
37
  getTheme: (theme?: Theme | (outerTheme: Theme) => void) => Theme
25
38
  outerTheme: Theme
26
- unsubscribeToOuter: () => void
39
+ unsubscribeToOuterId: string
27
40
  props: ThemeProviderProps
28
41
  broadcast: Broadcast
42
+ unsubscribeToOuterId: number = -1
29
43
 
30
44
  constructor() {
31
45
  super()
@@ -35,9 +49,9 @@ class ThemeProvider extends Component {
35
49
  componentWillMount() {
36
50
  // If there is a ThemeProvider wrapper anywhere around this theme provider, merge this theme
37
51
  // with the outer theme
38
- if (this.context[CHANNEL]) {
39
- const subscribe = this.context[CHANNEL]
40
- this.unsubscribeToOuter = subscribe(theme => {
52
+ const outerContext = this.context[CHANNEL_NEXT]
53
+ if (outerContext !== undefined) {
54
+ this.unsubscribeToOuterId = outerContext.subscribe(theme => {
41
55
  this.outerTheme = theme
42
56
  })
43
57
  }
@@ -45,7 +59,21 @@ class ThemeProvider extends Component {
45
59
  }
46
60
 
47
61
  getChildContext() {
48
- return { ...this.context, [CHANNEL]: this.broadcast.subscribe }
62
+ return {
63
+ ...this.context,
64
+ [CHANNEL_NEXT]: {
65
+ getTheme: this.getTheme,
66
+ subscribe: this.broadcast.subscribe,
67
+ unsubscribe: this.broadcast.unsubscribe,
68
+ },
69
+ [CHANNEL]: (subscriber) => {
70
+ warnChannelDeprecated()
71
+
72
+ // Patch the old `subscribe` provide via `CHANNEL` for older clients.
73
+ const unsubscribeId = this.broadcast.subscribe(subscriber)
74
+ return () => this.broadcast.unsubscribe(unsubscribeId)
75
+ },
76
+ }
49
77
  }
50
78
 
51
79
  componentWillReceiveProps(nextProps: ThemeProviderProps) {
@@ -53,8 +81,8 @@ class ThemeProvider extends Component {
53
81
  }
54
82
 
55
83
  componentWillUnmount() {
56
- if (this.context[CHANNEL]) {
57
- this.unsubscribeToOuter()
84
+ if (this.unsubscribeToOuterId !== -1) {
85
+ this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeToOuterId)
58
86
  }
59
87
  }
60
88
 
@@ -83,10 +111,11 @@ class ThemeProvider extends Component {
83
111
  }
84
112
 
85
113
  ThemeProvider.childContextTypes = {
86
- [CHANNEL]: PropTypes.func.isRequired,
114
+ [CHANNEL]: PropTypes.func, // legacy
115
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
87
116
  }
88
117
  ThemeProvider.contextTypes = {
89
- [CHANNEL]: PropTypes.func,
118
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
90
119
  }
91
120
 
92
121
  export default ThemeProvider
@@ -1,9 +1,8 @@
1
1
  // @flow
2
2
  /* eslint-disable react/no-multi-comp */
3
3
  import React from 'react'
4
- import PropTypes from 'prop-types'
5
4
  import { shallow, render } from 'enzyme'
6
- import ThemeProvider, { CHANNEL } from '../ThemeProvider'
5
+ import ThemeProvider, { CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from '../ThemeProvider'
7
6
 
8
7
  describe('ThemeProvider', () => {
9
8
  it('should not throw an error when no children are passed', () => {
@@ -31,7 +30,7 @@ describe('ThemeProvider', () => {
31
30
  // Setup Child
32
31
  class Child extends React.Component {
33
32
  componentWillMount() {
34
- this.context[CHANNEL](theme => {
33
+ this.context[CHANNEL_NEXT].subscribe(theme => {
35
34
  expect(theme).toEqual({ ...outerTheme, ...innerTheme })
36
35
  done()
37
36
  })
@@ -39,7 +38,7 @@ describe('ThemeProvider', () => {
39
38
  render() { return null }
40
39
  }
41
40
  Child.contextTypes = {
42
- [CHANNEL]: PropTypes.object,
41
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
43
42
  }
44
43
 
45
44
  render(
@@ -58,7 +57,7 @@ describe('ThemeProvider', () => {
58
57
  // Setup Child
59
58
  class Child extends React.Component {
60
59
  componentWillMount() {
61
- this.context[CHANNEL](theme => {
60
+ this.context[CHANNEL_NEXT].subscribe(theme => {
62
61
  expect(theme).toEqual({ ...outerestTheme, ...outerTheme, ...innerTheme })
63
62
  done()
64
63
  })
@@ -66,7 +65,7 @@ describe('ThemeProvider', () => {
66
65
  render() { return null }
67
66
  }
68
67
  Child.contextTypes = {
69
- [CHANNEL]: PropTypes.object,
68
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
70
69
  }
71
70
 
72
71
  render(
@@ -89,7 +88,7 @@ describe('ThemeProvider', () => {
89
88
  // Setup Child
90
89
  class Child extends React.Component {
91
90
  componentWillMount() {
92
- this.context[CHANNEL](theme => {
91
+ this.context[CHANNEL_NEXT].subscribe(theme => {
93
92
  // eslint-disable-next-line react/prop-types
94
93
  expect(theme).toEqual(themes[this.props.shouldHaveTheme])
95
94
  childRendered++ // eslint-disable-line no-plusplus
@@ -101,7 +100,7 @@ describe('ThemeProvider', () => {
101
100
  render() { return null }
102
101
  }
103
102
  Child.contextTypes = {
104
- [CHANNEL]: PropTypes.object,
103
+ [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
105
104
  }
106
105
 
107
106
  render(
@@ -21,7 +21,7 @@ const styled = (tag: Target) => constructWithOptions(StyledNativeComponent, tag)
21
21
  /* React native lazy-requires each of these modules for some reason, so let's
22
22
  * assume it's for a good reason and not eagerly load them all */
23
23
  const aliases = `ActivityIndicator ActivityIndicatorIOS ART Button DatePickerIOS DrawerLayoutAndroid
24
- Image ImageEditor ImageStore KeyboardAvoidingView ListView MapView Modal Navigator NavigatorIOS
24
+ Image ImageBackground ImageEditor ImageStore KeyboardAvoidingView ListView MapView Modal Navigator NavigatorIOS
25
25
  Picker PickerIOS ProgressBarAndroid ProgressViewIOS ScrollView SegmentedControlIOS Slider
26
26
  SliderIOS SnapshotViewIOS Switch RecyclerViewBackedScrollView RefreshControl StatusBar
27
27
  SwipeableListView SwitchAndroid SwitchIOS TabBarIOS Text TextInput ToastAndroid ToolbarAndroid
@@ -32,6 +32,20 @@ describe('native', () => {
32
32
  expect(view.prop('style')).toEqual([ { paddingTop: 10 }, style ])
33
33
  })
34
34
 
35
+ it('should not console.warn if a comment is seen', () => {
36
+ const oldConsoleWarn = console.warn;
37
+ console.warn = jest.fn();
38
+ try {
39
+ styled.View`
40
+ /* this is a comment */
41
+ `
42
+
43
+ expect(console.warn).not.toHaveBeenCalled();
44
+ } finally {
45
+ console.warn = oldConsoleWarn;
46
+ }
47
+ })
48
+
35
49
  describe('extending', () => {
36
50
  it('should combine styles of extending components', () => {
37
51
  const Parent = styled.View`opacity: 0.9;`
@@ -0,0 +1,147 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ssr should add a nonce to the stylesheet if webpack nonce is detected in the global scope 1`] = `"<h1 class=\\"sc-a b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"197727696\\">Hello SSR!</h1>"`;
4
+
5
+ exports[`ssr should add a nonce to the stylesheet if webpack nonce is detected in the global scope 2`] = `
6
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\" nonce=\\"foo\\">/* sc-component-id: sc-global-2303210225 */
7
+ body{background: papayawhip;}
8
+ </style><style type=\\"text/css\\" data-styled-components=\\"b\\" data-styled-components-is-local=\\"true\\" nonce=\\"foo\\">/* sc-component-id: sc-a */
9
+ .sc-a {}
10
+
11
+ .b{color: red;}
12
+ </style>"
13
+ `;
14
+
15
+ exports[`ssr should allow global styles to be injected during rendering 1`] = `"<h1 class=\\"PageOne a\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"2014320521\\">Camera One!</h1>"`;
16
+
17
+ exports[`ssr should allow global styles to be injected during rendering 2`] = `
18
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-737874422 */
19
+ html::before{content: 'Before both renders';}
20
+ </style><style type=\\"text/css\\" data-styled-components=\\"a\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: PageOne */
21
+ .PageOne {}
22
+
23
+ .a{color: red;}
24
+ </style><style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2914197427 */
25
+ html::before{content: 'During first render';}
26
+ </style>"
27
+ `;
28
+
29
+ exports[`ssr should allow global styles to be injected during rendering 3`] = `"<h2 class=\\"PageTwo b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"2124224444\\">Camera Two!</h2>"`;
30
+
31
+ exports[`ssr should allow global styles to be injected during rendering 4`] = `
32
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-737874422 */
33
+ html::before{content: 'Before both renders';}
34
+ </style><style type=\\"text/css\\" data-styled-components=\\"b\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: PageTwo */
35
+ .PageTwo {}
36
+
37
+ .b{color: blue;}
38
+ </style><style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2914197427 */
39
+ html::before{content: 'During first render';}
40
+ /* sc-component-id: sc-global-1207956261 */
41
+ html::before{content: 'Between renders';}
42
+ /* sc-component-id: sc-global-3990873394 */
43
+ html::before{content: 'During second render';}
44
+ </style>"
45
+ `;
46
+
47
+ exports[`ssr should dispatch global styles to each ServerStyleSheet 1`] = `"<h1 class=\\"Header a\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"1829114759\\"></h1>"`;
48
+
49
+ exports[`ssr should dispatch global styles to each ServerStyleSheet 2`] = `
50
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2303210225 */
51
+ body{background: papayawhip;}
52
+ </style><style type=\\"text/css\\" data-styled-components=\\"a\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: Header */
53
+ .Header {}
54
+
55
+ .a{-webkit-animation:keyframe_0 1s both;animation:keyframe_0 1s both;}
56
+ /* sc-component-id: sc-keyframes-keyframe_0 */
57
+ @-webkit-keyframes keyframe_0{0%{opacity: 0;}}@keyframes keyframe_0{0%{opacity: 0;}}
58
+ </style>"
59
+ `;
60
+
61
+ exports[`ssr should extract both global and local CSS 1`] = `"<h1 class=\\"sc-a b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"197727696\\">Hello SSR!</h1>"`;
62
+
63
+ exports[`ssr should extract both global and local CSS 2`] = `
64
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2303210225 */
65
+ body{background: papayawhip;}
66
+ </style><style type=\\"text/css\\" data-styled-components=\\"b\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: sc-a */
67
+ .sc-a {}
68
+
69
+ .b{color: red;}
70
+ </style>"
71
+ `;
72
+
73
+ exports[`ssr should extract the CSS in a simple case 1`] = `"<h1 class=\\"sc-a b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"197727696\\">Hello SSR!</h1>"`;
74
+
75
+ exports[`ssr should extract the CSS in a simple case 2`] = `
76
+ "<style type=\\"text/css\\" data-styled-components=\\"b\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: sc-a */
77
+ .sc-a {}
78
+
79
+ .b{color: red;}
80
+ </style>"
81
+ `;
82
+
83
+ exports[`ssr should render CSS in the order the components were defined, not rendered 1`] = `"<div data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"275982144\\"><h2 class=\\"TWO a\\" data-reactid=\\"2\\"></h2><h1 class=\\"ONE b\\" data-reactid=\\"3\\"></h1></div>"`;
84
+
85
+ exports[`ssr should render CSS in the order the components were defined, not rendered 2`] = `
86
+ "<style type=\\"text/css\\" data-styled-components=\\"a b\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: ONE */
87
+ .ONE {}
88
+
89
+ .b{color: red;}
90
+ /* sc-component-id: TWO */
91
+ .TWO {}
92
+
93
+ .a{color: blue;}
94
+ </style>"
95
+ `;
96
+
97
+ exports[`ssr should return a generated React style element 1`] = `
98
+ Object {
99
+ "dangerouslySetInnerHTML": Object {
100
+ "__html": "/* sc-component-id: sc-global-2303210225 */
101
+ body{background: papayawhip;}
102
+ ",
103
+ },
104
+ "data-styled-components": "",
105
+ "data-styled-components-is-local": "false",
106
+ "type": "text/css",
107
+ }
108
+ `;
109
+
110
+ exports[`ssr should return a generated React style element 2`] = `
111
+ Object {
112
+ "dangerouslySetInnerHTML": Object {
113
+ "__html": "/* sc-component-id: sc-a */
114
+ .sc-a {}
115
+
116
+ .b{color: red;}
117
+ ",
118
+ },
119
+ "data-styled-components": "b",
120
+ "data-styled-components-is-local": "true",
121
+ "type": "text/css",
122
+ }
123
+ `;
124
+
125
+ exports[`ssr should share global styles but keep renders separate 1`] = `"<h1 class=\\"PageOne a\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"2014320521\\">Camera One!</h1>"`;
126
+
127
+ exports[`ssr should share global styles but keep renders separate 2`] = `
128
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2303210225 */
129
+ body{background: papayawhip;}
130
+ </style><style type=\\"text/css\\" data-styled-components=\\"a\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: PageOne */
131
+ .PageOne {}
132
+
133
+ .a{color: red;}
134
+ </style>"
135
+ `;
136
+
137
+ exports[`ssr should share global styles but keep renders separate 3`] = `"<h2 class=\\"PageTwo b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"2124224444\\">Camera Two!</h2>"`;
138
+
139
+ exports[`ssr should share global styles but keep renders separate 4`] = `
140
+ "<style type=\\"text/css\\" data-styled-components=\\"\\" data-styled-components-is-local=\\"false\\">/* sc-component-id: sc-global-2303210225 */
141
+ body{background: papayawhip;}
142
+ </style><style type=\\"text/css\\" data-styled-components=\\"b\\" data-styled-components-is-local=\\"true\\">/* sc-component-id: PageTwo */
143
+ .PageTwo {}
144
+
145
+ .b{color: blue;}
146
+ </style>"
147
+ `;
@@ -62,6 +62,30 @@ describe('expanded api', () => {
62
62
  expect(Comp2.styledComponentId).toBe('Comp2-OMGLOL')
63
63
  expect(shallow(<Comp2 />).prop('className')).toMatch(/Comp2-OMGLOL/)
64
64
  })
65
+
66
+ it('should work with `.extend`', () => {
67
+ const Comp = styled.div.withConfig({ displayName: 'Comp', componentId: 'LOLOMG' })`
68
+ color: blue;
69
+ `
70
+ const Comp2 = Comp.extend`
71
+ color: ${'red'};
72
+ background: ${props => props.bg};
73
+ `
74
+ expect(Comp.styledComponentId).toBe('Comp-LOLOMG')
75
+ expect(shallow(<Comp />).prop('className')).toMatch(/Comp-LOLOMG/)
76
+ expect(Comp2.styledComponentId).toBe('LOLOMG-Comp-a')
77
+ expect(shallow(<Comp2 bg="red" />).prop('className')).toMatch(/LOLOMG-Comp-a/)
78
+ })
79
+
80
+ it('should work with `.withComponent`', () => {
81
+ const Dummy = () => null
82
+ const Comp = styled.div.withConfig({ displayName: 'Comp', componentId: 'OMGLOL' })``.withComponent('h1')
83
+ const Comp2 = styled.div.withConfig({ displayName: 'Comp2', componentId: 'OMFG' })``.withComponent(Dummy)
84
+ expect(Comp.styledComponentId).toBe('Comp-OMGLOL-h1')
85
+ expect(shallow(<Comp />).prop('className')).toMatch(/Comp-OMGLOL-h1/)
86
+ expect(Comp2.styledComponentId).toBe('Comp2-OMFG-Dummy')
87
+ expect(shallow(<Comp2 />).prop('className')).toMatch(/Comp2-OMFG-Dummy/)
88
+ })
65
89
  })
66
90
 
67
91
  describe('chaining', () => {
@@ -19,10 +19,21 @@ describe('props', () => {
19
19
  expectCSSMatches('.sc-a {} .b { color: black; }')
20
20
  })
21
21
  it('should execute interpolations and inject props', () => {
22
+ const Comp = styled.div`color: ${props => props.fg || 'black'};`
23
+ shallow(<Comp fg="red" />)
24
+ expectCSSMatches('.sc-a {} .b { color: red; }')
25
+ })
26
+ it('should ignore non-0 falsy object interpolations', () => {
22
27
  const Comp = styled.div`
23
- color: ${props => props.fg || 'black'};
28
+ ${() => ({
29
+ borderWidth: 0,
30
+ colorA: null,
31
+ colorB: false,
32
+ colorC: undefined,
33
+ colorD: '',
34
+ })};
24
35
  `
25
- shallow(<Comp fg="red"/>)
26
- expectCSSMatches('.sc-a {} .b { color: red; }')
36
+ shallow(<Comp fg="red" />)
37
+ expectCSSMatches('.sc-a {} .b { border-width: 0; ; ; }')
27
38
  })
28
39
  })