Repository: tkh44/emotion
Pull request #39 (2017-06-28 10:04:51)

Add injectGlobal and other stuff
#### `injectGlobal`
This adds `injectGlobal` which as the name suggests inserts global styles. It works like this:
```jsx
injectGlobal`
  body {
    margin: 0;
    padding: 0;
  }
`
```
It's basically the same as [`styled-components`' `injectGlobal`](https://www.styled-components.com/docs/api#injectglobal). Also, this allows people to use `@font-face`, so should we remove `fontFace`?(it doesn't really matter that much because `fontFace` is pretty small `export const fontFace = injectGlobal`)

#### Other Stuff

##### Allow interpolation in:
- [x] `injectGlobal`
- [x] `fontFace`
- [x] `keyframes`

##### Statically extract if there are no vars or `@apply`s:
- [x] `injectGlobal`
- [x] `fontFace`
- [x] `keyframes`

##### Misc
- [x] split `babel.test.js` into multiple files(it's 550 lines)


Patch:
diff --git a/src/babel.js b/src/babel.js
index f5592b25..e8e6e7d8 100644
--- a/src/babel.js
+++ b/src/babel.js
@@ -1,7 +1,7 @@
 import fs from 'fs'
 import { basename } from 'path'
 import { touchSync } from 'touch'
-import { inline, keyframes, fontFace } from './inline'
+import { inline, keyframes, fontFace, injectGlobal } from './inline'
 import findAndReplaceAttrs from './attrs'
 
 function parseDynamicValues (rules, t) {
@@ -112,9 +112,8 @@ export default function (babel) {
         // });
 
         const parent = path.findParent(p => p.isVariableDeclarator())
-        const identifierName = parent && t.isIdentifier(parent.node.id)
-          ? parent.node.id.name
-          : ''
+        const identifierName =
+          parent && t.isIdentifier(parent.node.id) ? parent.node.id.name : ''
 
         function buildCallExpression (identifier, tag, path) {
           const built = findAndReplaceAttrs(path, t)
@@ -243,56 +242,101 @@ export default function (babel) {
           t.isIdentifier(path.node.tag) &&
           path.node.tag.name === 'keyframes'
         ) {
-          const { hash, name, rules } = keyframes(
+          const { hash, name, rules, isStatic } = keyframes(
             path.node.quasi,
             identifierName,
-            'animation'
+            'animation',
+            state.inline
           )
-
-          path.replaceWith(
-            t.callExpression(t.identifier('keyframes'), [
-              t.stringLiteral(`${name}-${hash}`),
-              t.arrayExpression(path.node.quasi.expressions),
-              t.functionExpression(
-                t.identifier('createEmotionKeyframe'),
-                path.node.quasi.expressions.map((x, i) =>
-                  t.identifier(`x${i}`)
-                ),
-                t.blockStatement([
-                  t.returnStatement(
-                    t.arrayExpression(rules.map(r => t.stringLiteral(r)))
-                  )
-                ])
-              )
+          const animationName = `${name}-${hash}`
+          if (isStatic) {
+            state.insertStaticRules([
+              `@keyframes ${animationName} ${rules.join('')}`
             ])
-          )
+            path.replaceWith(t.stringLiteral(animationName))
+          } else {
+            path.replaceWith(
+              t.callExpression(t.identifier('keyframes'), [
+                t.stringLiteral(animationName),
+                t.arrayExpression(path.node.quasi.expressions),
+                t.functionExpression(
+                  t.identifier('createEmotionKeyframes'),
+                  path.node.quasi.expressions.map((x, i) =>
+                    t.identifier(`x${i}`)
+                  ),
+                  t.blockStatement([
+                    t.returnStatement(
+                      t.arrayExpression(parseDynamicValues(rules, t))
+                    )
+                  ])
+                )
+              ])
+            )
+          }
         } else if (
           t.isIdentifier(path.node.tag) &&
           path.node.tag.name === 'fontFace'
         ) {
-          const { hash, name, rules } = fontFace(
+          const { rules, isStatic } = fontFace(path.node.quasi, state.inline)
+          if (isStatic) {
+            state.insertStaticRules(rules)
+            if (t.isExpressionStatement(path.parent)) {
+              path.parentPath.remove()
+            } else {
+              path.replaceWith(t.identifier('undefined'))
+            }
+          } else {
+            path.replaceWith(
+              t.callExpression(t.identifier('fontFace'), [
+                t.arrayExpression(path.node.quasi.expressions),
+                t.functionExpression(
+                  t.identifier('createEmotionFontFace'),
+                  path.node.quasi.expressions.map((x, i) =>
+                    t.identifier(`x${i}`)
+                  ),
+                  t.blockStatement([
+                    t.returnStatement(
+                      t.arrayExpression(parseDynamicValues(rules, t))
+                    )
+                  ])
+                )
+              ])
+            )
+          }
+        } else if (
+          t.isIdentifier(path.node.tag) &&
+          path.node.tag.name === 'injectGlobal' &&
+          t.isTemplateLiteral(path.node.quasi)
+        ) {
+          const { isStatic, rules } = injectGlobal(
             path.node.quasi,
-            identifierName,
-            'font-face'
-          )
-
-          path.replaceWith(
-            t.callExpression(t.identifier('fontFace'), [
-              t.stringLiteral(`${name}-${hash}`),
-              t.arrayExpression(path.node.quasi.expressions),
-              t.functionExpression(
-                t.identifier('createEmotionFontFace'),
-                path.node.quasi.expressions.map((x, i) =>
-                  t.identifier(`x${i}`)
-                ),
-                t.blockStatement([
-                  t.returnStatement(
-                    t.arrayExpression(rules.map(r => t.stringLiteral(r)))
-                  )
-                ])
-              )
-            ])
+            state.inline
           )
+          if (isStatic) {
+            state.insertStaticRules(rules)
+            if (t.isExpressionStatement(path.parent)) {
+              path.parentPath.remove()
+            } else {
+              path.replaceWith(t.identifier('undefined'))
+            }
+          } else {
+            path.replaceWith(
+              t.callExpression(t.identifier('injectGlobal'), [
+                t.arrayExpression(path.node.quasi.expressions),
+                t.functionExpression(
+                  t.identifier('createEmotionGlobal'),
+                  path.node.quasi.expressions.map((x, i) =>
+                    t.identifier(`x${i}`)
+                  ),
+                  t.blockStatement([
+                    t.returnStatement(
+                      t.arrayExpression(parseDynamicValues(rules, t))
+                    )
+                  ])
+                )
+              ])
+            )
+          }
         }
       }
     }
diff --git a/src/index.js b/src/index.js
index 22e54ce8..ffc244ad 100644
--- a/src/index.js
+++ b/src/index.js
@@ -49,36 +49,32 @@ export function css (cls: string, vars: vars, content: () => string[]) {
   return cls + (vars && vars.length > 0 ? ' ' + values(cls, vars) : '')
 }
 
-export function fragment (vars: vars, content: () => string[]) {
+export function fragment (vars: vars, content: () => string) {
   return content(...vars)
 }
 
-export function keyframes (kfm: string, vars: vars, content: () => string[]) {
-  let src = content(...vars)
-  let hash = hashArray(src)
+export function injectGlobal (vars: vars, content: () => string[]) {
+  const src = content(...vars)
+  const hash = hashArray(src)
   if (!inserted[hash]) {
     inserted[hash] = true
-    src.forEach(r => {
-      return sheet.insert(`@keyframes ${kfm}-${hash} {${r}}`)
-    })
+    sheet.insert(src)
   }
-  return `${kfm}-${hash}`
 }
 
-export function fontFace (
-  fontRules: string,
-  vars: vars,
-  content: () => string[]
-) {
-  let src = content(...vars)
-  let hash = hashArray(src)
+export const fontFace = injectGlobal
+
+export function keyframes (kfm: string, vars: vars, content: () => string[]) {
+  const src = content(...vars)
+  const hash = hashArray(src)
+  const animationName = `${kfm}-${hash}`
   if (!inserted[hash]) {
     inserted[hash] = true
     src.forEach(r => {
-      return sheet.insert(r)
+      sheet.insert(`@keyframes ${animationName} ${r}`)
     })
   }
-  return `${fontRules}-${hash}`
+  return animationName
 }
 
 export function hydrate (ids: string[]) {
diff --git a/src/inline.js b/src/inline.js
index 637968e7..cfb01511 100644
--- a/src/inline.js
+++ b/src/inline.js
@@ -25,6 +25,24 @@ function getName (
   return parts.join('-')
 }
 
+function replaceVarsWithPlaceholders (rules: string[]): string[] {
+  return rules.map(rule =>
+    rule.replace(
+      /var\(--[A-Za-z0-9-_]+-([0-9]+)\)/gm,
+      (match, p1) => `xxx${p1}xxx`
+    )
+  )
+}
+
+function replaceApplyWithPlaceholders (rules: string[]): string[] {
+  return rules.map(rule =>
+    rule.replace(
+      /@apply\s+--[A-Za-z0-9-_]+-([0-9]+)/gm,
+      (match, p1) => `xxx${p1}xxx`
+    )
+  )
+}
+
 export function inline (
   quasi: any,
   identifierName?: string,
@@ -56,67 +74,101 @@ export function inline (
     .trim()
 
   let rules = parseCSS(`.${name}-${hash} { ${src} }`)
-  if (hasApply) {
-    rules = rules.map(rule =>
-      rule.replace(
-        /@apply\s+--[A-Za-z0-9-_]+-([0-9]+)/gm,
-        (match, p1) => `xxx${p1}xxx`
-      )
-    )
-  }
   if (inlineVars || hasApply) {
-    rules = rules.map(rule =>
-      rule.replace(
-        /var\(--[A-Za-z0-9-_]+-([0-9]+)\)/gm,
-        (match, p1) => `xxx${p1}xxx`
-      )
-    )
+    rules = replaceVarsWithPlaceholders(rules)
+  }
+  if (hasApply) {
+    rules = replaceApplyWithPlaceholders(rules)
   }
-
   return { hash, name, rules, isStatic: !hasApply && !inlineVars }
 }
 
 export function keyframes (
   quasi: any,
   identifierName?: string,
-  prefix: string
+  prefix: string,
+  inlineVars: boolean
 ): { hash: string, name: string, rules: string[] } {
-  let strs = quasi.quasis.map(x => x.value.cooked)
-  let hash = hashArray([...strs])
-  let name = getName(
+  const strs = quasi.quasis.map(x => x.value.cooked)
+  const hash = hashArray([...strs])
+  const name = getName(
     extractNameFromProperty(strs.join('xxx')),
     identifierName,
     prefix
   )
-
-  return {
-    hash,
-    name,
-    rules: [parseCSS(`{ ${strs.join('').trim()} }`).join('').trim()]
+  let hasApplyOrVar = false
+  const src = strs
+    .reduce((arr, str, i) => {
+      arr.push(str)
+      if (i !== quasi.expressions.length) {
+        hasApplyOrVar = true
+        // todo - test for preceding @apply
+        let applyMatch = /@apply\s*$/gm.exec(str)
+        if (applyMatch) {
+          arr.push(`--name-hash-${i}`)
+        } else arr.push(`var(--name-hash-${i})`)
+      }
+      return arr
+    }, [])
+    .join('')
+    .trim()
+  let rules = parseCSS(`{ ${src} }`, { nested: false })
+  if (hasApplyOrVar) {
+    rules = replaceVarsWithPlaceholders(rules)
+    rules = replaceApplyWithPlaceholders(rules)
   }
+  return { hash, name, rules, isStatic: !hasApplyOrVar && !inlineVars }
 }
 
 export function fontFace (
   quasi: any,
-  identifierName?: string,
-  prefix: string
-): { hash: string, name: string, rules: string[] } {
+  inlineVars: boolean
+): { rules: string[], isStatic: boolean } {
   let strs = quasi.quasis.map(x => x.value.cooked)
-  let hash = hashArray([...strs])
-  let name = getName(
-    extractNameFromProperty(strs.join('xxx')),
-    identifierName,
-    prefix
-  )
-  return {
-    hash,
-    name,
-    rules: [
-      parseCSS(`@font-face {${strs.join('').trim()}}`, {
-        nested: false
-      })
-        .join('')
-        .trim()
-    ]
+  let hasVar
+  let src = strs
+    .reduce((arr, str, i) => {
+      arr.push(str)
+      if (i !== quasi.expressions.length) {
+        hasVar = true
+        arr.push(`var(--name-hash-${i})`)
+      }
+      return arr
+    }, [])
+    .join('')
+    .trim()
+  let rules = parseCSS(`@font-face {${src}}`)
+  if (hasVar) {
+    rules = replaceVarsWithPlaceholders(rules)
+  }
+  return { rules, isStatic: !inlineVars && !hasVar }
+}
+
+export function injectGlobal (
+  quasi: any,
+  inlineVars: boolean
+): { rules: string[], isStatic: boolean } {
+  let strs = quasi.quasis.map(x => x.value.cooked)
+  let hasApplyOrVar = false
+  let src = strs
+    .reduce((arr, str, i) => {
+      arr.push(str)
+      if (i !== quasi.expressions.length) {
+        hasApplyOrVar = true
+        // todo - test for preceding @apply
+        let applyMatch = /@apply\s*$/gm.exec(str)
+        if (applyMatch) {
+          arr.push(`--name-hash-${i}`)
+        } else arr.push(`var(--name-hash-${i})`)
+      }
+      return arr
+    }, [])
+    .join('')
+    .trim()
+  let rules = parseCSS(src)
+  if (hasApplyOrVar) {
+    rules = replaceVarsWithPlaceholders(rules)
+    rules = replaceApplyWithPlaceholders(rules)
   }
+  return { rules, isStatic: !inlineVars && !hasApplyOrVar }
 }


Test patch:
diff --git a/test/__snapshots__/babel.test.js.snap b/test/__snapshots__/babel.test.js.snap
deleted file mode 100644
index 8e431786..00000000
--- a/test/__snapshots__/babel.test.js.snap
+++ /dev/null
@@ -1,260 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`emotion/babel babel css css basic 1`] = `
-"
-css(\\"css-cu78iu\\", [widthVar], function createEmotionRules(x0) {
-        return [\`.css-cu78iu { margin: 12px 48px;
-        color: #ffffff;
-        display: -webkit-box;
-        display: -moz-box;
-        display: -ms-flexbox;
-        display: -webkit-flex;
-        display: flex;
-        -webkit-flex: 1 0 auto;
-        -ms-flex: 1 0 auto;
-        flex: 1 0 auto;
-        color: blue;
-        width: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel css css kitchen sink 1`] = `
-"
-const cls = css(\\"css-cls-h5t3cu\\", [margin], function createEmotionRules(x0) {
-        return [\`.css-cls-h5t3cu { font-size: 58pt;margin: \${x0}; }\`];
-});
-const frag = fragment([], function createEmotionFragment() {
-        return \` padding: 8px; \`;
-});
-const fragB = fragment([heightVar, frag], function createEmotionFragment(x0, x1) {
-        return \` height: \${x0};\${x1}; \`;
-});
-const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRules(x0, x1) {
-        return [\`.css-cls2-1b4c3x0 { \${x0};
-        \${x1};
-        margin: 12px 48px;
-        color: #ffffff; }\`];
-});"
-`;
-
-exports[`emotion/babel babel css extract css basic 1`] = `
-"import \\"./babel.test.emotion.css\\";
-
-css(\\"css-cu78iu\\", [widthVar]);"
-`;
-
-exports[`emotion/babel babel css extract css basic 2`] = `
-".css-cu78iu { margin: 12px 48px;
-        color: #ffffff;
-        display: -webkit-box;
-        display: -moz-box;
-        display: -ms-flexbox;
-        display: -webkit-flex;
-        display: flex;
-        -webkit-flex: 1 0 auto;
-        -ms-flex: 1 0 auto;
-        flex: 1 0 auto;
-        color: blue;
-        width: var(--css-cu78iu-0); }"
-`;
-
-exports[`emotion/babel babel css extract css kitchen sink 1`] = `
-"import \\"./babel.test.emotion.css\\";
-
-const cls = css(\\"css-cls-h5t3cu\\", [margin]);
-const frag = fragment([], function createEmotionFragment() {
-        return \` padding: 8px; \`;
-});
-const fragB = fragment([heightVar, frag], function createEmotionFragment(x0, x1) {
-        return \` height: \${x0};\${x1}; \`;
-});
-const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRules(x0, x1) {
-        return [\`.css-cls2-1b4c3x0 { \${x0};
-        \${x1};
-        margin: 12px 48px;
-        color: #ffffff; }\`];
-});"
-`;
-
-exports[`emotion/babel babel css extract css kitchen sink 2`] = `".css-cls-h5t3cu { font-size: 58pt;margin: var(--css-cls-h5t3cu-0); }"`;
-
-exports[`emotion/babel babel font-face font-face basic 1`] = `
-"
-fontFace(\\"font-face-1o0czbr\\", [], function createEmotionFontFace() {
-     return [\\"@font-face {font-family: MyHelvetica;\\\\n          src: local(\\\\\\"Helvetica Neue Bold\\\\\\"),\\\\n               local(\\\\\\"HelveticaNeue-Bold\\\\\\"),\\\\n               url(MgOpenModernaBold.ttf);\\\\n          font-weight: bold;}\\"];
-});"
-`;
-
-exports[`emotion/babel babel fragment basic fragment 1`] = `
-"
-const frag = fragment([], function createEmotionFragment() {
-        return \` color: green \`;
-});
-styled(\\"h1\\", \\"css-u3lc8x\\", [frag], function createEmotionStyledRules(x0) {
-        return [\`.css-u3lc8x { font-size: 20px; \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel fragment fragment kitchen sink 1`] = `
-"
-const frag = fragment([backgroundColor], function createEmotionFragment(x0) {
-        return \` color: green; background-color: \${x0} \`;
-});
-const frag1 = fragment([], function createEmotionFragment() {
-        return \` width: 20px; \`;
-});
-const frag2 = fragment([frag1], function createEmotionFragment(x0) {
-        return \` height: 20px; \${x0}; \`;
-});
-styled('h1', 'css-some-name-9k8moo', [fontSize + 'px', frag, frag2], function createEmotionStyledRules(x0, x1, x2) {
-        return [\`.css-some-name-9k8moo { font-size: \${x0}; \${x1}; \${x2} }\`];
-});"
-`;
-
-exports[`emotion/babel babel fragment fragment with multiple selectors that should throw 1`] = `"unknown: Fragments cannot have multiple selectors."`;
-
-exports[`emotion/babel babel keyframes keyframe basic 1`] = `
-"
-const rotate360 = keyframes(\\"animation-rotate360-f35ahc\\", [], function createEmotionKeyframe() {
-  return [\\"from {\\\\n    -webkit-transform: rotate(0deg);\\\\n    -ms-transform: rotate(0deg);\\\\n    transform: rotate(0deg);\\\\n} to {\\\\n    -webkit-transform: rotate(360deg);\\\\n    -ms-transform: rotate(360deg);\\\\n    transform: rotate(360deg);\\\\n}\\"];
-});"
-`;
-
-exports[`emotion/babel babel styled component attr 1`] = `
-"styled('input', 'css-14vghnb', [function getMargin(props) {
-       return props.margin;
-}, props => props.height * props.scale, function getWidth(props) {
-       return props.width;
-}, flex], function createEmotionStyledRules(x0, x1, x2, x3) {
-       return [\`.css-14vghnb { margin: \${x0};
-       color: #ffffff;
-       height: \${x1};
-       width: \${x2};
-       color: blue;
-       display: \${x3}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component attr kitchen sink 1`] = `
-"styled('input', 'css-1lqib20', [function getMargin(props) {
-        return props.margin ? props.margin : ('16' + 'px');
-}, function getPadding(props) {
-        return props.padding ? props.padding : ('16' + 'em');
-}, function getWidth(props) {
-        return props.width ? props.width : ('95' + '%');
-}, function getHeight(props) {
-        return props.height ? props.height : ('90' + 'vw');
-}, function getDisplay(props) {
-        return props.display ? props.display : ('flex' + '');
-}], function createEmotionStyledRules(x0, x1, x2, x3, x4) {
-        return [\`.css-1lqib20 { margin: \${x0};
-        padding: \${x1};
-        font-size: attr(fontSize ch, 8);
-        width: \${x2};
-        height: \${x3};
-        display: \${x4}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component attr with default value 1`] = `
-"styled('input', 'css-1luxokk', [function getMargin(props) {
-        return props.margin ? props.margin : ('16' + '');
-}], function createEmotionStyledRules(x0) {
-        return [\`.css-1luxokk { margin: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component attr with value type 1`] = `
-"styled('input', 'css-1luxokk', [function getMargin(props) {
-        return props.margin + 'px';
-}], function createEmotionStyledRules(x0) {
-        return [\`.css-1luxokk { margin: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component attr with value type and default value 1`] = `
-"styled('input', 'css-1luxokk', [function getMargin(props) {
-        return props.margin ? props.margin : ('16' + 'px');
-}], function createEmotionStyledRules(x0) {
-        return [\`.css-1luxokk { margin: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component basic 1`] = `
-"const H1 = styled('h1', 'css-H1-1t8i2zo', [fontSize + 'px'], function createEmotionStyledRules(x0) {
-  return [\`.css-H1-1t8i2zo { font-size: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component extract based on props 1`] = `
-"import './babel.test.emotion.css';
-const H1 = styled('h1', 'css-H1-q3ohdq', [fontSize + 'px', props => props.translateX]);"
-`;
-
-exports[`emotion/babel babel styled component extract based on props 2`] = `".css-H1-q3ohdq { font-size: var(--css-H1-q3ohdq-0); height: 20px; -webkit-transform: translateX(var(--css-H1-q3ohdq-1)); -ms-transform: translateX(var(--css-H1-q3ohdq-1)); transform: translateX(var(--css-H1-q3ohdq-1)) }"`;
-
-exports[`emotion/babel babel styled component extract basic 1`] = `
-"import './babel.test.emotion.css';
-const H1 = styled('h1', 'css-H1-1mh2el5', [fontSize + 'px']);"
-`;
-
-exports[`emotion/babel babel styled component extract basic 2`] = `".css-H1-1mh2el5 { font-size: var(--css-H1-1mh2el5-0); height: 20px }"`;
-
-exports[`emotion/babel babel styled component extract no dynamic 1`] = `
-"import \\"./babel.test.emotion.css\\";
-styled(\\"h1\\", \\"css-14ksm7b\\", []);"
-`;
-
-exports[`emotion/babel babel styled component extract no dynamic 2`] = `".css-14ksm7b { color: blue; }"`;
-
-exports[`emotion/babel babel styled component extract no use 1`] = `"\\"h1\\";"`;
-
-exports[`emotion/babel babel styled component extract with fragment 1`] = `
-"const frag1 = fragment([], function createEmotionFragment() {
-  return \` width: 20px; \`;
-});
-const H1 = styled('h1', 'css-H1-osx1au', [fontSize + 'px', props => props.translateX, frag1], function createEmotionStyledRules(x0, x1, x2) {
-  return [\`.css-H1-osx1au { font-size: \${x0};
-        height: 20px;
-        -webkit-transform: translateX(\${x1});
-        -ms-transform: translateX(\${x1});
-        transform: translateX(\${x1});
-        \${x2} }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component extract with fragment 2`] = `".css-H1-q3ohdq { font-size: var(--css-H1-q3ohdq-0); height: 20px; -webkit-transform: translateX(var(--css-H1-q3ohdq-1)); -ms-transform: translateX(var(--css-H1-q3ohdq-1)); transform: translateX(var(--css-H1-q3ohdq-1)) }"`;
-
-exports[`emotion/babel babel styled component function call 1`] = `
-"styled(MyComponent, 'css-1t8i2zo', [fontSize + 'px'], function createEmotionStyledRules(x0) {
-  return [\`.css-1t8i2zo { font-size: \${x0}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component match works on multiple 1`] = `
-"styled('input', 'css-1j6h68f', [function getMargin(props) {
-        return props.margin ? props.margin : ('16' + 'px');
-}, function getPadding(props) {
-        return props.padding ? props.padding : ('16' + 'em');
-}], function createEmotionStyledRules(x0, x1) {
-        return [\`.css-1j6h68f { margin: \${x0};
-        color: blue;
-        padding: \${x1}; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component name is correct with no identifier 1`] = `
-"
-css(\\"css-1cppx8p\\", [], function createEmotionRules() {
-        return [\`.css-1cppx8p { margin: 12px 48px;
-        color: #ffffff; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component no dynamic 1`] = `
-"styled(\\"h1\\", \\"css-14ksm7b\\", [], function createEmotionStyledRules() {
-  return [\`.css-14ksm7b { color: blue; }\`];
-});"
-`;
-
-exports[`emotion/babel babel styled component no use 1`] = `"\\"h1\\";"`;
diff --git a/test/__snapshots__/font-face.test.js.snap b/test/__snapshots__/font-face.test.js.snap
index b34e6aa7..5dc1c850 100644
--- a/test/__snapshots__/font-face.test.js.snap
+++ b/test/__snapshots__/font-face.test.js.snap
@@ -1,3 +1,21 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`font-face adds font-face to sheet 1`] = `Array []`;
+exports[`font-face adds font-face to sheet 1`] = `
+"@font-face {font-family: 'Patrick Hand SC';
+      font-style: normal;
+      font-weight: 400;
+      src: local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');
+      unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}"
+`
+
+exports[`font-face adds font-face to sheet with interpolation 1`] = `
+"@font-face {font-family: 'Patrick Hand SC';
+      font-style: normal;
+      font-weight: 400;
+      src: local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');
+      unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}@font-face {font-family: MyHelvetica;
+      src: local(\\"Helvetica Neue Bold\\"),
+          local(\\"HelveticaNeue-Bold\\"),
+          url(MgOpenModernaBold.ttf);
+      font-weight: bold;}"
+`
diff --git a/test/__snapshots__/inject-global.test.js.snap b/test/__snapshots__/inject-global.test.js.snap
new file mode 100644
index 00000000..09c0a7f8
--- /dev/null
+++ b/test/__snapshots__/inject-global.test.js.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`injectGlobal 1`] = `
+"html {
+      background: pink;
+    }body {
+      color: yellow
+       margin: 0;
+    padding: 0; 
+    }"
+`;
diff --git a/test/__snapshots__/keyframes.test.js.snap b/test/__snapshots__/keyframes.test.js.snap
index bdd25434..6eaf4c91 100644
--- a/test/__snapshots__/keyframes.test.js.snap
+++ b/test/__snapshots__/keyframes.test.js.snap
@@ -1,14 +1,58 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`keyframes keyframes with interpolation 1`] = `
+"@keyframes animation-bounce-10a3qiv-cmo0tx { from, 20%, 53%, 80%, to {
+        -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+        animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+        -webkit-transform: translate3d(0,0,0);
+        -ms-transform: translate3d(0,0,0);
+        transform: translate3d(0,0,0);
+      }
+    
+      40%, 43% {
+        -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+        animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+        -webkit-transform: translate3d(0, -30px, 0);
+        -ms-transform: translate3d(0, -30px, 0);
+        transform: translate3d(0, -30px, 0);
+      }
+    
+      70% {
+        -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+        animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+        -webkit-transform: translate3d(0, -15px, 0);
+        -ms-transform: translate3d(0, -15px, 0);
+        transform: translate3d(0, -15px, 0);
+      }
+    
+      90% {
+        -webkit-transform: translate3d(0,-4px,0);
+        -ms-transform: translate3d(0,-4px,0);
+        transform: translate3d(0,-4px,0);
+      } }.css-H1-o0kcx2-fy8ana { font-size: 20px;
+      -webkit-animation: animation-bounce-10a3qiv-cmo0tx 2s linear infinite;
+      animation: animation-bounce-10a3qiv-cmo0tx 2s linear infinite; }@keyframes animation-1fpnjxj-1mmv2re { from {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+    
+      to {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      } }"
+`;
+
 exports[`keyframes renders 1`] = `
-.css-H1-o0kcx2-5hrnwy {
+.css-H1-o0kcx2-fy8ana {
   font-size: 20px;
-  -webkit-animation: animation-bounce-10a3qiv-c1ph90 2s linear infinite;
-  animation: animation-bounce-10a3qiv-c1ph90 2s linear infinite;
+  -webkit-animation: animation-bounce-10a3qiv-cmo0tx 2s linear infinite;
+  animation: animation-bounce-10a3qiv-cmo0tx 2s linear infinite;
 }
 
 <h1
-  className="css-H1-o0kcx2-5hrnwy"
+  className="css-H1-o0kcx2-fy8ana"
 >
   hello world
 </h1>
diff --git a/test/__snapshots__/parser.test.js.snap b/test/__snapshots__/parser.test.js.snap
index 953e275b..9dc968e8 100644
--- a/test/__snapshots__/parser.test.js.snap
+++ b/test/__snapshots__/parser.test.js.snap
@@ -15,7 +15,7 @@ Array [
   width: var(--css-hash-0);
 }",
 ]
-`;
+`
 
 exports[`parser fancy 1`] = `
 Array [
@@ -52,7 +52,7 @@ Array [
   content: 'pseudo'
 }",
 ]
-`;
+`
 
 exports[`parser static 1`] = `
 Array [
@@ -62,4 +62,4 @@ Array [
   width: 30px
 }",
 ]
-`;
+`
diff --git a/test/__snapshots__/styled.test.js.snap b/test/__snapshots__/styled.test.js.snap
index e31c9642..b6eccad7 100644
--- a/test/__snapshots__/styled.test.js.snap
+++ b/test/__snapshots__/styled.test.js.snap
@@ -13,12 +13,12 @@ exports[`styled attr 1`] = `
 `;
 
 exports[`styled basic render 1`] = `
-.css-H1-13wdnau-xy96vj {
+.css-H1-8xpzga-1e133rd {
   font-size: 20px;
 }
 
 <h1
-  className="css-H1-13wdnau-xy96vj"
+  className="css-H1-8xpzga-1e133rd"
 >
   hello world
 </h1>
@@ -121,13 +121,18 @@ exports[`styled name 1`] = `
 `;
 
 exports[`styled no dynamic 1`] = `
-.css-H1-dae0kw-kdrkuq {
+.css-H1-ijh7uz-10q50rl {
   font-size: 12px;
 }
 
 <h1
-  className="css-H1-dae0kw-kdrkuq"
+  className="css-H1-ijh7uz-10q50rl"
 >
   hello world
 </h1>
 `;
+
+exports[`styled throws if undefined is passed as the component 1`] = `
+"You are trying to create a styled element with an undefined component.
+You may have forgotten to import it."
+`;
diff --git a/test/babel/__snapshots__/css.test.js.snap b/test/babel/__snapshots__/css.test.js.snap
new file mode 100644
index 00000000..22a3d37b
--- /dev/null
+++ b/test/babel/__snapshots__/css.test.js.snap
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`babel css extract css basic 1`] = `
+"import \\"./css.test.emotion.css\\";
+
+css(\\"css-cu78iu\\", [widthVar]);"
+`;
+
+exports[`babel css extract css basic 2`] = `
+".css-cu78iu { margin: 12px 48px;
+        color: #ffffff;
+        display: -webkit-box;
+        display: -moz-box;
+        display: -ms-flexbox;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-flex: 1 0 auto;
+        -ms-flex: 1 0 auto;
+        flex: 1 0 auto;
+        color: blue;
+        width: var(--css-cu78iu-0); }"
+`;
+
+exports[`babel css extract css kitchen sink 1`] = `
+"
+const frag = fragment([], function createEmotionFragment() {
+        return \` padding: 8px; \`;
+});
+const fragB = fragment([heightVar, frag], function createEmotionFragment(x0, x1) {
+        return \` height: \${x0};\${x1}; \`;
+});
+const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRules(x0, x1) {
+        return [\`.css-cls2-1b4c3x0 { \${x0};
+        \${x1};
+        margin: 12px 48px;
+        color: #ffffff; }\`];
+});"
+`;
+
+exports[`babel css inline css basic 1`] = `
+"
+css(\\"css-cu78iu\\", [widthVar], function createEmotionRules(x0) {
+        return [\`.css-cu78iu { margin: 12px 48px;
+        color: #ffffff;
+        display: -webkit-box;
+        display: -moz-box;
+        display: -ms-flexbox;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-flex: 1 0 auto;
+        -ms-flex: 1 0 auto;
+        flex: 1 0 auto;
+        color: blue;
+        width: \${x0}; }\`];
+});"
+`;
+
+exports[`babel css inline css kitchen sink 1`] = `
+"
+const cls = css(\\"css-cls-h5t3cu\\", [margin], function createEmotionRules(x0) {
+        return [\`.css-cls-h5t3cu { font-size: 58pt;margin: \${x0}; }\`];
+});
+const frag = fragment([], function createEmotionFragment() {
+        return \` padding: 8px; \`;
+});
+const fragB = fragment([heightVar, frag], function createEmotionFragment(x0, x1) {
+        return \` height: \${x0};\${x1}; \`;
+});
+const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRules(x0, x1) {
+        return [\`.css-cls2-1b4c3x0 { \${x0};
+        \${x1};
+        margin: 12px 48px;
+        color: #ffffff; }\`];
+});"
+`;
diff --git a/test/babel/__snapshots__/font-face.test.js.snap b/test/babel/__snapshots__/font-face.test.js.snap
new file mode 100644
index 00000000..75c2cd9e
--- /dev/null
+++ b/test/babel/__snapshots__/font-face.test.js.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fontFace babel extract basic 1`] = `"import \\"./font-face.test.emotion.css\\";"`;
+
+exports[`fontFace babel extract basic 2`] = `
+"@font-face {font-family: MyHelvetica;
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;}"
+`;
+
+exports[`fontFace babel extract basic assign to variable 1`] = `
+"import \\"./font-face.test.emotion.css\\";
+
+const thisWillBeUndefined = undefined;"
+`;
+
+exports[`fontFace babel extract basic assign to variable 2`] = `
+"@font-face {font-family: MyHelvetica;
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;}"
+`;
+
+exports[`fontFace babel extract interpolation 1`] = `
+"
+fontFace([fontFamilyName], function createEmotionFontFace(x0) {
+     return [\`@font-face {font-family: \${x0};
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;}\`];
+});"
+`;
+
+exports[`fontFace babel inline basic 1`] = `
+"
+fontFace([], function createEmotionFontFace() {
+     return [\`@font-face {font-family: MyHelvetica;
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;}\`];
+});"
+`;
+
+exports[`fontFace babel inline interpolation 1`] = `
+"
+fontFace([fontFamilyName], function createEmotionFontFace(x0) {
+     return [\`@font-face {font-family: \${x0};
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;}\`];
+});"
+`;
diff --git a/test/babel/__snapshots__/fragment.test.js.snap b/test/babel/__snapshots__/fragment.test.js.snap
new file mode 100644
index 00000000..43205560
--- /dev/null
+++ b/test/babel/__snapshots__/fragment.test.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`babel fragment basic fragment 1`] = `
+"
+const frag = fragment([], function createEmotionFragment() {
+      return \` color: green \`;
+});
+styled(\\"h1\\", \\"css-u3lc8x\\", [frag], function createEmotionStyledRules(x0) {
+      return [\`.css-u3lc8x { font-size: 20px; \${x0}; }\`];
+});"
+`;
+
+exports[`babel fragment fragment kitchen sink 1`] = `
+"
+const frag = fragment([backgroundColor], function createEmotionFragment(x0) {
+      return \` color: green; background-color: \${x0} \`;
+});
+const frag1 = fragment([], function createEmotionFragment() {
+      return \` width: 20px; \`;
+});
+const frag2 = fragment([frag1], function createEmotionFragment(x0) {
+      return \` height: 20px; \${x0}; \`;
+});
+styled('h1', 'css-some-name-9k8moo', [fontSize + 'px', frag, frag2], function createEmotionStyledRules(x0, x1, x2) {
+      return [\`.css-some-name-9k8moo { font-size: \${x0}; \${x1}; \${x2} }\`];
+});"
+`;
+
+exports[`babel fragment fragment with multiple selectors that should throw 1`] = `"unknown: Fragments cannot have multiple selectors."`;
diff --git a/test/__snapshots__/babel-fs.test.js.snap b/test/babel/__snapshots__/fs.test.js.snap
similarity index 90%
rename from test/__snapshots__/babel-fs.test.js.snap
rename to test/babel/__snapshots__/fs.test.js.snap
index 53f45a60..f7817782 100644
--- a/test/__snapshots__/babel-fs.test.js.snap
+++ b/test/babel/__snapshots__/fs.test.js.snap
@@ -16,13 +16,13 @@ exports[`babel plugin fs creates and writes to the css file when it does not exi
 `;
 
 exports[`babel plugin fs creates and writes to the css file when it does not exist 2`] = `
-"import \\"./babel-fs.test.emotion.css\\";
+"import \\"./fs.test.emotion.css\\";
 
 css(\\"css-class-lk4ur6\\", [widthVar]);"
 `;
 
 exports[`babel plugin fs does not write to the css file when it is the same as is already written 1`] = `
-"import \\"./babel-fs.test.emotion.css\\";
+"import \\"./fs.test.emotion.css\\";
 
 css(\\"css-class-lk4ur6\\", [widthVar]);"
 `;
@@ -43,7 +43,7 @@ exports[`babel plugin fs writes to the css file when it does exist  1`] = `
 `;
 
 exports[`babel plugin fs writes to the css file when it does exist  2`] = `
-"import \\"./babel-fs.test.emotion.css\\";
+"import \\"./fs.test.emotion.css\\";
 
 css(\\"css-class-lk4ur6\\", [widthVar]);"
 `;
diff --git a/test/babel/__snapshots__/inject-global.test.js.snap b/test/babel/__snapshots__/inject-global.test.js.snap
new file mode 100644
index 00000000..745652b5
--- /dev/null
+++ b/test/babel/__snapshots__/inject-global.test.js.snap
@@ -0,0 +1,94 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`babel injectGlobal extract injectGlobal assign to variable 1`] = `
+"import \\"./inject-global.test.emotion.css\\";
+
+const thisWillBeUndefined = undefined;"
+`;
+
+exports[`babel injectGlobal extract injectGlobal assign to variable 2`] = `
+"body {
+            margin: 0;
+            padding: 0;
+          }
+body > div {
+            display: none;
+}
+html {
+            background: green;
+          }"
+`;
+
+exports[`babel injectGlobal extract injectGlobal basic 1`] = `"import \\"./inject-global.test.emotion.css\\";"`;
+
+exports[`babel injectGlobal extract injectGlobal basic 2`] = `
+"body {
+            margin: 0;
+            padding: 0;
+          }
+body > div {
+            display: none;
+}
+html {
+            background: green;
+          }"
+`;
+
+exports[`babel injectGlobal extract injectGlobal with interpolation 1`] = `
+"
+injectGlobal([display], function createEmotionGlobal(x0) {
+  return [\`body {
+            margin: 0;
+            padding: 0;
+            display: \${x0};
+          }\`, \`body > div {
+            display: none;
+}\`, \`html {
+            background: green;
+          }\`];
+});"
+`;
+
+exports[`babel injectGlobal inline injectGlobal basic 1`] = `
+"
+injectGlobal([], function createEmotionGlobal() {
+  return [\`body {
+            margin: 0;
+            padding: 0;
+          }\`, \`body > div {
+            display: none;
+}\`, \`html {
+            background: green;
+          }\`];
+});"
+`;
+
+exports[`babel injectGlobal inline injectGlobal with fragment 1`] = `
+"
+injectGlobal([display, htmlStyled], function createEmotionGlobal(x0, x1) {
+  return [\`body {
+            margin: 0;
+            padding: 0;
+            display: \${x0};
+          }\`, \`body > div {
+            display: none;
+}\`, \`html {
+            \${x1}
+          }\`];
+});"
+`;
+
+exports[`babel injectGlobal inline injectGlobal with interpolation 1`] = `
+"
+injectGlobal([display], function createEmotionGlobal(x0) {
+  return [\`body {
+            margin: 0;
+            padding: 0;
+            display: \${x0};
+          }\`, \`body > div {
+            display: none;
+}\`, \`html {
+            background: green;
+          }\`];
+});"
+`;
diff --git a/test/babel/__snapshots__/keyframes.test.js.snap b/test/babel/__snapshots__/keyframes.test.js.snap
new file mode 100644
index 00000000..a03e7673
--- /dev/null
+++ b/test/babel/__snapshots__/keyframes.test.js.snap
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`babel keyframes extract keyframes basic 1`] = `
+"import \\"./keyframes.test.emotion.css\\";
+
+const rotate360 = \\"animation-rotate360-f35ahc\\";"
+`;
+
+exports[`babel keyframes extract keyframes basic 2`] = `
+"@keyframes animation-rotate360-f35ahc { from {
+            -webkit-transform: rotate(0deg);
+            -ms-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+        
+          to {
+            -webkit-transform: rotate(360deg);
+            -ms-transform: rotate(360deg);
+            transform: rotate(360deg);
+          } }"
+`;
+
+exports[`babel keyframes extract keyframes with interpolation 1`] = `
+"
+const rotate360 = keyframes(\\"animation-rotate360-9bsj7q\\", [endingRotation], function createEmotionKeyframes(x0) {
+  return [\`{ from {
+            -webkit-transform: rotate(0deg);
+            -ms-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+        
+          to {
+            -webkit-transform: rotate(\${x0});
+            -ms-transform: rotate(\${x0});
+            transform: rotate(\${x0});
+          } }\`];
+});"
+`;
+
+exports[`babel keyframes inline keyframes basic 1`] = `
+"
+const rotate360 = keyframes(\\"animation-rotate360-10gy9ar\\", [], function createEmotionKeyframes() {
+  return [\`{ from {
+            -webkit-transform: rotate(0deg);
+            -ms-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          to {
+            -webkit-transform: rotate(360deg);
+            -ms-transform: rotate(360deg);
+            transform: rotate(360deg);
+          } }\`];
+});"
+`;
+
+exports[`babel keyframes inline keyframes with fragment 1`] = `
+"
+const rotate360 = keyframes(\\"animation-rotate360-b0ufjw\\", [endingRotation, frag1], function createEmotionKeyframes(x0, x1) {
+  return [\`{ from {
+            -webkit-transform: rotate(0deg);
+            -ms-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          to {
+            -webkit-transform: rotate(\${x0});
+            -ms-transform: rotate(\${x0});
+            transform: rotate(\${x0});
+            \${x1}
+          } }\`];
+});"
+`;
+
+exports[`babel keyframes inline keyframes with interpolation 1`] = `
+"
+const rotate360 = keyframes(\\"animation-rotate360-r2494l\\", [endingRotation], function createEmotionKeyframes(x0) {
+  return [\`{ from {
+            -webkit-transform: rotate(0deg);
+            -ms-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          to {
+            -webkit-transform: rotate(\${x0});
+            -ms-transform: rotate(\${x0});
+            transform: rotate(\${x0});
+          } }\`];
+});"
+`;
diff --git a/test/babel/__snapshots__/styled.test.js.snap b/test/babel/__snapshots__/styled.test.js.snap
new file mode 100644
index 00000000..4c275c2d
--- /dev/null
+++ b/test/babel/__snapshots__/styled.test.js.snap
@@ -0,0 +1,138 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`babel styled component extract based on props 1`] = `
+"import './styled.test.emotion.css';
+const H1 = styled('h1', 'css-H1-q3ohdq', [fontSize + 'px', props => props.translateX]);"
+`;
+
+exports[`babel styled component extract based on props 2`] = `".css-H1-q3ohdq { font-size: var(--css-H1-q3ohdq-0); height: 20px; -webkit-transform: translateX(var(--css-H1-q3ohdq-1)); -ms-transform: translateX(var(--css-H1-q3ohdq-1)); transform: translateX(var(--css-H1-q3ohdq-1)) }"`;
+
+exports[`babel styled component extract basic 1`] = `
+"import './styled.test.emotion.css';
+const H1 = styled('h1', 'css-H1-1mh2el5', [fontSize + 'px']);"
+`;
+
+exports[`babel styled component extract basic 2`] = `".css-H1-1mh2el5 { font-size: var(--css-H1-1mh2el5-0); height: 20px }"`;
+
+exports[`babel styled component extract no dynamic 1`] = `
+"import \\"./styled.test.emotion.css\\";
+styled(\\"h1\\", \\"css-14ksm7b\\", []);"
+`;
+
+exports[`babel styled component extract no dynamic 2`] = `".css-14ksm7b { color: blue; }"`;
+
+exports[`babel styled component extract no use 1`] = `"\\"h1\\";"`;
+
+exports[`babel styled component extract with fragment 1`] = `
+"const frag1 = fragment([], function createEmotionFragment() {
+  return \` width: 20px; \`;
+});
+const H1 = styled('h1', 'css-H1-osx1au', [fontSize + 'px', props => props.translateX, frag1], function createEmotionStyledRules(x0, x1, x2) {
+  return [\`.css-H1-osx1au { font-size: \${x0};
+        height: 20px;
+        -webkit-transform: translateX(\${x1});
+        -ms-transform: translateX(\${x1});
+        transform: translateX(\${x1});
+        \${x2} }\`];
+});"
+`;
+
+exports[`babel styled component inline attr 1`] = `
+"styled('input', 'css-14vghnb', [function getMargin(props) {
+       return props.margin;
+}, props => props.height * props.scale, function getWidth(props) {
+       return props.width;
+}, flex], function createEmotionStyledRules(x0, x1, x2, x3) {
+       return [\`.css-14vghnb { margin: \${x0};
+       color: #ffffff;
+       height: \${x1};
+       width: \${x2};
+       color: blue;
+       display: \${x3}; }\`];
+});"
+`;
+
+exports[`babel styled component inline attr kitchen sink 1`] = `
+"styled('input', 'css-1lqib20', [function getMargin(props) {
+        return props.margin ? props.margin : ('16' + 'px');
+}, function getPadding(props) {
+        return props.padding ? props.padding : ('16' + 'em');
+}, function getWidth(props) {
+        return props.width ? props.width : ('95' + '%');
+}, function getHeight(props) {
+        return props.height ? props.height : ('90' + 'vw');
+}, function getDisplay(props) {
+        return props.display ? props.display : ('flex' + '');
+}], function createEmotionStyledRules(x0, x1, x2, x3, x4) {
+        return [\`.css-1lqib20 { margin: \${x0};
+        padding: \${x1};
+        font-size: attr(fontSize ch, 8);
+        width: \${x2};
+        height: \${x3};
+        display: \${x4}; }\`];
+});"
+`;
+
+exports[`babel styled component inline attr with default value 1`] = `
+"styled('input', 'css-1luxokk', [function getMargin(props) {
+        return props.margin ? props.margin : ('16' + '');
+}], function createEmotionStyledRules(x0) {
+        return [\`.css-1luxokk { margin: \${x0}; }\`];
+});"
+`;
+
+exports[`babel styled component inline attr with value type 1`] = `
+"styled('input', 'css-1luxokk', [function getMargin(props) {
+        return props.margin + 'px';
+}], function createEmotionStyledRules(x0) {
+        return [\`.css-1luxokk { margin: \${x0}; }\`];
+});"
+`;
+
+exports[`babel styled component inline attr with value type and default value 1`] = `
+"styled('input', 'css-1luxokk', [function getMargin(props) {
+        return props.margin ? props.margin : ('16' + 'px');
+}], function createEmotionStyledRules(x0) {
+        return [\`.css-1luxokk { margin: \${x0}; }\`];
+});"
+`;
+
+exports[`babel styled component inline basic 1`] = `
+"const H1 = styled('h1', 'css-H1-1t8i2zo', [fontSize + 'px'], function createEmotionStyledRules(x0) {
+  return [\`.css-H1-1t8i2zo { font-size: \${x0}; }\`];
+});"
+`;
+
+exports[`babel styled component inline function call 1`] = `
+"styled(MyComponent, 'css-1t8i2zo', [fontSize + 'px'], function createEmotionStyledRules(x0) {
+  return [\`.css-1t8i2zo { font-size: \${x0}; }\`];
+});"
+`;
+
+exports[`babel styled component inline match works on multiple 1`] = `
+"styled('input', 'css-1j6h68f', [function getMargin(props) {
+        return props.margin ? props.margin : ('16' + 'px');
+}, function getPadding(props) {
+        return props.padding ? props.padding : ('16' + 'em');
+}], function createEmotionStyledRules(x0, x1) {
+        return [\`.css-1j6h68f { margin: \${x0};
+        color: blue;
+        padding: \${x1}; }\`];
+});"
+`;
+
+exports[`babel styled component inline name is correct with no identifier 1`] = `
+"
+css(\\"css-1cppx8p\\", [], function createEmotionRules() {
+        return [\`.css-1cppx8p { margin: 12px 48px;
+        color: #ffffff; }\`];
+});"
+`;
+
+exports[`babel styled component inline no dynamic 1`] = `
+"styled(\\"h1\\", \\"css-14ksm7b\\", [], function createEmotionStyledRules() {
+  return [\`.css-14ksm7b { color: blue; }\`];
+});"
+`;
+
+exports[`babel styled component inline no use 1`] = `"\\"h1\\";"`;
diff --git a/test/babel/css.test.js b/test/babel/css.test.js
new file mode 100644
index 00000000..e57d7955
--- /dev/null
+++ b/test/babel/css.test.js
@@ -0,0 +1,86 @@
+/* eslint-env jest */
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
+jest.mock('fs')
+
+fs.existsSync.mockReturnValue(true)
+
+describe('babel css', () => {
+  describe('inline', () => {
+    test('css basic', () => {
+      const basic = `
+        css\`
+        margin: 12px 48px;
+        color: #ffffff;
+        display: flex;
+        flex: 1 0 auto;
+        color: blue;
+        width: \${widthVar};
+      \``
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+
+    test('css kitchen sink', () => {
+      const basic = `
+        const cls = css\`font-size: 58pt;margin: \${margin};\`
+        const frag = fragment\`padding: 8px;\`
+        const fragB = fragment\`height: \${heightVar};@apply \${frag};\`
+        const cls2 = css\`
+        @apply $\{frag};
+        @apply $\{fragB};
+        margin: 12px 48px;
+        color: #ffffff;
+        \`
+      `
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+  })
+  describe('extract', () => {
+    test('css basic', () => {
+      const basic = `
+        css\`
+        margin: 12px 48px;
+        color: #ffffff;
+        display: flex;
+        flex: 1 0 auto;
+        color: blue;
+        width: \${widthVar};
+      \``
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+      expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot()
+    })
+
+    test('css kitchen sink', () => {
+      const basic = `
+        const frag = fragment\`padding: 8px;\`
+        const fragB = fragment\`height: \${heightVar};@apply \${frag};\`
+        const cls2 = css\`
+        @apply \${frag};
+        @apply \${fragB};
+        margin: 12px 48px;
+        color: #ffffff;
+        \`
+      `
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/test/babel/font-face.test.js b/test/babel/font-face.test.js
new file mode 100644
index 00000000..79cd3d88
--- /dev/null
+++ b/test/babel/font-face.test.js
@@ -0,0 +1,95 @@
+/* eslint-env jest */
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
+jest.mock('fs')
+
+fs.existsSync.mockReturnValue(true)
+
+describe('fontFace babel', () => {
+  describe('inline', () => {
+    test('basic', () => {
+      const basic = `
+        fontFace\`
+          font-family: MyHelvetica;
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+    test('interpolation', () => {
+      const basic = `
+        fontFace\`
+          font-family: \${fontFamilyName};
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+  })
+  describe('extract', () => {
+    test('basic', () => {
+      const basic = `
+        fontFace\`
+          font-family: MyHelvetica;
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+      expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot()
+    })
+    test('basic assign to variable', () => {
+      const basic = `
+        const thisWillBeUndefined = fontFace\`
+          font-family: MyHelvetica;
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(2)
+      expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot()
+    })
+    test('interpolation', () => {
+      const basic = `
+        fontFace\`
+          font-family: \${fontFamilyName};
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        babelrc: false,
+        filename: __filename
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(2)
+    })
+  })
+})
diff --git a/test/babel/fragment.test.js b/test/babel/fragment.test.js
new file mode 100644
index 00000000..1f8db817
--- /dev/null
+++ b/test/babel/fragment.test.js
@@ -0,0 +1,51 @@
+/* eslint-env jest */
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
+jest.mock('fs')
+
+fs.existsSync.mockReturnValue(true)
+
+describe('babel fragment', () => {
+  test('basic fragment', () => {
+    const basic = `
+      const frag = fragment\`color: green\`;
+      styled.h1\`font-size: 20px; @apply \${frag};\``
+    const { code } = babel.transform(basic, {
+      plugins: [plugin]
+    })
+    expect(code).toMatchSnapshot()
+  })
+
+  test('fragment kitchen sink', () => {
+    const basic = `
+      const frag = fragment\`color: green; background-color: \${backgroundColor}\`;
+      const frag1 = fragment\` width: 20px; name: some-frag-name; \`
+      const frag2 = fragment\` height: 20px; @apply \${frag1}; \`
+      styled.h1\`font-size: \${fontSize + 'px'}; name: some-name; @apply \${frag}; @apply \${frag2}\``
+    const { code } = babel.transform(basic, {
+      plugins: [plugin]
+    })
+    expect(code).toMatchSnapshot()
+  })
+
+  test('fragment with multiple selectors that should throw', () => {
+    expect(() => {
+      const basic = `
+        const frag = fragment\`
+          color: green;
+          display: none;
+          &:hover {
+            color: yellow;
+          }
+          & .wow {
+            color: purple;
+          }
+        \`;
+      `
+      babel.transform(basic, {
+        plugins: [plugin]
+      })
+    }).toThrowErrorMatchingSnapshot()
+  })
+})
diff --git a/test/babel-fs.test.js b/test/babel/fs.test.js
similarity index 98%
rename from test/babel-fs.test.js
rename to test/babel/fs.test.js
index a01e58c4..6e22fcff 100644
--- a/test/babel-fs.test.js
+++ b/test/babel/fs.test.js
@@ -2,7 +2,7 @@
 import { transform } from 'babel-core'
 import fs from 'fs'
 import touch from 'touch'
-import emotionPlugin from '../src/babel'
+import emotionPlugin from '../../src/babel'
 
 jest.mock('fs').mock('touch')
 
diff --git a/test/babel/inject-global.test.js b/test/babel/inject-global.test.js
new file mode 100644
index 00000000..61aa901b
--- /dev/null
+++ b/test/babel/inject-global.test.js
@@ -0,0 +1,140 @@
+/* eslint-env jest */
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
+jest.mock('fs')
+
+fs.existsSync.mockReturnValue(true)
+
+describe('babel injectGlobal', () => {
+  describe('inline', () => {
+    test('injectGlobal basic', () => {
+      const basic = `
+        injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            background: green;
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+    test('injectGlobal with interpolation', () => {
+      const basic = `
+        injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            display: \${display};
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            background: green;
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+    test('injectGlobal with fragment', () => {
+      const basic = `
+        injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            display: \${display};
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            @apply \${htmlStyled}
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+  })
+  describe('extract', () => {
+    test('injectGlobal basic', () => {
+      const basic = `
+        injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            background: green;
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+      expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot()
+    })
+    test('injectGlobal assign to variable', () => {
+      const basic = `
+        const thisWillBeUndefined = injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            background: green;
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(2)
+      expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot()
+    })
+    test('injectGlobal with interpolation', () => {
+      const basic = `
+        injectGlobal\`
+          body {
+            margin: 0;
+            padding: 0;
+            display: \${display};
+            & > div {
+              display: none;
+            }
+          }
+          html {
+            background: green;
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(2)
+    })
+  })
+})
diff --git a/test/babel/keyframes.test.js b/test/babel/keyframes.test.js
new file mode 100644
index 00000000..dd5dc576
--- /dev/null
+++ b/test/babel/keyframes.test.js
@@ -0,0 +1,99 @@
+/* eslint-env jest */
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
+jest.mock('fs')
+
+fs.existsSync.mockReturnValue(true)
+
+describe('babel keyframes', () => {
+  describe('inline', () => {
+    test('keyframes basic', () => {
+      const basic = `
+        const rotate360 = keyframes\`
+          from {
+            transform: rotate(0deg);
+          }
+          to {
+            transform: rotate(360deg);
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+    test('keyframes with interpolation', () => {
+      const basic = `
+        const rotate360 = keyframes\`
+          from {
+            transform: rotate(0deg);
+          }
+          to {
+            transform: rotate(\${endingRotation});
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+    test('keyframes with fragment', () => {
+      const basic = `
+        const rotate360 = keyframes\`
+          from {
+            transform: rotate(0deg);
+          }
+          to {
+            transform: rotate(\${endingRotation});
+            @apply \${frag1}
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
+  })
+  describe('extract', () => {
+    test('keyframes basic', () => {
+      const basic = `
+        const rotate360 = keyframes\`
+          from {
+            transform: rotate(0deg);
+          }
+        
+          to {
+            transform: rotate(360deg);
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        babelrc: false,
+        filename: __filename
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+      expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot()
+    })
+    test('keyframes with interpolation', () => {
+      const basic = `
+        const rotate360 = keyframes\`
+          from {
+            transform: rotate(0deg);
+          }
+        
+          to {
+            transform: rotate(\${endingRotation});
+          }
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        babelrc: false,
+        filename: __filename
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/test/babel.test.js b/test/babel/styled.test.js
similarity index 51%
rename from test/babel.test.js
rename to test/babel/styled.test.js
index 4da31233..92488e75 100644
--- a/test/babel.test.js
+++ b/test/babel/styled.test.js
@@ -1,15 +1,14 @@
-/* eslint-disable jsx-quotes,no-useless-escape,no-template-curly-in-string */
+/* eslint-disable no-template-curly-in-string */
 /* eslint-env jest */
-const babel = require('babel-core')
-const plugin = require('../src/babel')
-const fs = require('fs')
-
+import * as babel from 'babel-core'
+import plugin from '../../src/babel'
+import * as fs from 'fs'
 jest.mock('fs')
 
 fs.existsSync.mockReturnValue(true)
 
-describe('emotion/babel', () => {
-  describe('babel styled component', () => {
+describe('babel styled component', () => {
+  describe('inline', () => {
     test('no use', () => {
       const basic = 'styled.h1``'
       const { code } = babel.transform(basic, {
@@ -59,10 +58,10 @@ describe('emotion/babel', () => {
       const basic = `styled('input')\`
        margin: attr(margin);
        color: #ffffff;
-       height: \$\{props => props.height * props.scale\};
+       height: \${props => props.height * props.scale};
        width: attr(width);
        color: blue;
-       display: \$\{flex\};
+       display: \${flex};
       \``
       const { code } = babel.transform(basic, {
         plugins: [plugin]
@@ -127,42 +126,7 @@ describe('emotion/babel', () => {
       expect(code).toMatchSnapshot()
     })
   })
-  describe('babel css', () => {
-    test('css basic', () => {
-      const basic = `
-        css\`
-        margin: 12px 48px;
-        color: #ffffff;
-        display: flex;
-        flex: 1 0 auto;
-        color: blue;
-        width: \$\{widthVar\};
-      \``
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]]
-      })
-      expect(code).toMatchSnapshot()
-    })
-
-    test('css kitchen sink', () => {
-      const basic = `
-        const cls = css\`font-size: 58pt;margin: $\{margin};\`
-        const frag = fragment\`padding: 8px;\`
-        const fragB = fragment\`height: $\{heightVar};@apply \$\{frag};\`
-        const cls2 = css\`
-        @apply $\{frag};
-        @apply $\{fragB};
-        margin: 12px 48px;
-        color: #ffffff;
-        \`
-      `
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]]
-      })
-      expect(code).toMatchSnapshot()
-    })
-  })
-  describe('babel styled component extract', () => {
+  describe('extract', () => {
     test('no use', () => {
       const basic = 'styled.h1``'
       const { code } = babel.transform(basic, {
@@ -226,128 +190,6 @@ describe('emotion/babel', () => {
       })
       expect(code).toMatchSnapshot()
       expect(fs.writeFileSync).toHaveBeenCalledTimes(3)
-      expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot()
-    })
-  })
-  describe('babel css extract', () => {
-    test('css basic', () => {
-      const basic = `
-        css\`
-        margin: 12px 48px;
-        color: #ffffff;
-        display: flex;
-        flex: 1 0 auto;
-        color: blue;
-        width: \$\{widthVar\};
-      \``
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]],
-        filename: __filename,
-        babelrc: false
-      })
-      expect(code).toMatchSnapshot()
-      expect(fs.writeFileSync).toHaveBeenCalledTimes(4)
-      expect(fs.writeFileSync.mock.calls[3][1]).toMatchSnapshot()
-    })
-
-    test('css kitchen sink', () => {
-      const basic = `
-        const cls = css\`font-size: 58pt;margin: $\{margin};\`
-        const frag = fragment\`padding: 8px;\`
-        const fragB = fragment\`height: $\{heightVar};@apply \$\{frag};\`
-        const cls2 = css\`
-        @apply $\{frag};
-        @apply $\{fragB};
-        margin: 12px 48px;
-        color: #ffffff;
-        \`
-      `
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]],
-        filename: __filename,
-        babelrc: false
-      })
-      expect(code).toMatchSnapshot()
-      expect(fs.writeFileSync).toHaveBeenCalledTimes(5)
-      expect(fs.writeFileSync.mock.calls[4][1]).toMatchSnapshot()
-    })
-  })
-  describe('babel fragment', () => {
-    test('basic fragment', () => {
-      const basic = `
-        const frag = fragment\`color: green\`;
-        styled.h1\`font-size: 20px; @apply \${frag};\``
-      const { code } = babel.transform(basic, {
-        plugins: [plugin]
-      })
-      expect(code).toMatchSnapshot()
-    })
-
-    test('fragment kitchen sink', () => {
-      const basic = `
-        const frag = fragment\`color: green; background-color: \${backgroundColor}\`;
-        const frag1 = fragment\` width: 20px; name: some-frag-name; \`
-        const frag2 = fragment\` height: 20px; @apply \${frag1}; \`
-        styled.h1\`font-size: \${fontSize + 'px'}; name: some-name; @apply \${frag}; @apply \${frag2}\``
-      const { code } = babel.transform(basic, {
-        plugins: [plugin]
-      })
-      expect(code).toMatchSnapshot()
-    })
-
-    test('fragment with multiple selectors that should throw', () => {
-      expect(() => {
-        const basic = `
-          const frag = fragment\`
-            color: green;
-            display: none;
-            &:hover {
-              color: yellow;
-            }
-            & .wow {
-              color: purple;
-            }
-          \`;
-        `
-        babel.transform(basic, {
-          plugins: [plugin]
-        })
-      }).toThrowErrorMatchingSnapshot()
-    })
-  })
-
-  describe('babel keyframes', () => {
-    test('keyframe basic', () => {
-      const basic = `
-        const rotate360 = keyframes\`
-          from {
-            transform: rotate(0deg);
-          }
-        
-          to {
-            transform: rotate(360deg);
-          }
-      \`;`
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]]
-      })
-      expect(code).toMatchSnapshot()
-    })
-  })
-  describe('babel font-face', () => {
-    test('font-face basic', () => {
-      const basic = `
-        fontFace\`
-          font-family: MyHelvetica;
-          src: local("Helvetica Neue Bold"),
-               local("HelveticaNeue-Bold"),
-               url(MgOpenModernaBold.ttf);
-          font-weight: bold;
-      \`;`
-      const { code } = babel.transform(basic, {
-        plugins: [[plugin]]
-      })
-      expect(code).toMatchSnapshot()
     })
   })
 })
diff --git a/test/extract/__snapshots__/extract.test.js.snap b/test/extract/__snapshots__/extract.test.js.snap
index 8b05ad3a..4c675095 100644
--- a/test/extract/__snapshots__/extract.test.js.snap
+++ b/test/extract/__snapshots__/extract.test.js.snap
@@ -13,12 +13,12 @@ exports[`styled attr 1`] = `
 `;
 
 exports[`styled basic render 1`] = `
-.vars-fnvbv0 {
-  --css-H1-13wdnau-0: 20;
+.vars-v8l2zy {
+  --css-H1-8xpzga-0: 20;
 }
 
 <h1
-  className="css-H1-13wdnau vars-fnvbv0"
+  className="css-H1-8xpzga vars-v8l2zy"
 >
   hello world
 </h1>
@@ -118,15 +118,15 @@ exports[`styled name 1`] = `
 
 exports[`styled no dynamic 1`] = `
 <h1
-  className="css-H1-dae0kw"
+  className="css-H1-ijh7uz"
 >
   hello world
 </h1>
 `;
 
 exports[`styled writes the correct css 1`] = `
-".css-H1-dae0kw { font-size: 12px; }
-.css-H1-13wdnau { font-size: var(--css-H1-13wdnau-0)px; }
+".css-H1-ijh7uz { font-size: 12px; }
+.css-H1-8xpzga { font-size: var(--css-H1-8xpzga-0)px; }
 .css-FancyH1-1azfbv1 {
       font-size: var(--css-FancyH1-1azfbv1-0)px; }
 .css-H1-15vt02v { font-size: var(--css-H1-15vt02v-0);
@@ -137,5 +137,8 @@ exports[`styled writes the correct css 1`] = `
 .css-H1-13wdnau { font-size: var(--css-H1-13wdnau-0)px; }
 .css-H2-vxb7tq { font-size: var(--css-H2-vxb7tq-0) }
 .css-H2-idm3bz { font-size: 32px; }
-.css-Content-13wdnau { font-size: var(--css-Content-13wdnau-0)px; }"
+.css-Content-13wdnau { font-size: var(--css-Content-13wdnau-0)px; }
+html {
+        background: pink;
+      }"
 `;
diff --git a/test/extract/extract.test.emotion.css b/test/extract/extract.test.emotion.css
index c5edd5bb..90ce6709 100644
--- a/test/extract/extract.test.emotion.css
+++ b/test/extract/extract.test.emotion.css
@@ -1,5 +1,5 @@
-.css-H1-dae0kw { font-size: 12px; }
-.css-H1-13wdnau { font-size: var(--css-H1-13wdnau-0)px; }
+.css-H1-ijh7uz { font-size: 12px; }
+.css-H1-8xpzga { font-size: var(--css-H1-8xpzga-0)px; }
 .css-FancyH1-1azfbv1 {
       font-size: var(--css-FancyH1-1azfbv1-0)px; }
 .css-H1-15vt02v { font-size: var(--css-H1-15vt02v-0);
@@ -10,4 +10,7 @@
 .css-H1-13wdnau { font-size: var(--css-H1-13wdnau-0)px; }
 .css-H2-vxb7tq { font-size: var(--css-H2-vxb7tq-0) }
 .css-H2-idm3bz { font-size: 32px; }
-.css-Content-13wdnau { font-size: var(--css-Content-13wdnau-0)px; }
\ No newline at end of file
+.css-Content-13wdnau { font-size: var(--css-Content-13wdnau-0)px; }
+html {
+        background: pink;
+      }
\ No newline at end of file
diff --git a/test/extract/extract.test.js b/test/extract/extract.test.js
index b30ef176..e9270bfe 100644
--- a/test/extract/extract.test.js
+++ b/test/extract/extract.test.js
@@ -4,9 +4,7 @@ import React from 'react'
 import renderer from 'react-test-renderer'
 import { basename } from 'path'
 import { matcher, serializer } from '../../jest-utils'
-
-// eslint-disable-next-line no-unused-vars
-import css, { fragment } from '../../src/index'
+import { fragment, injectGlobal } from '../../src/index'
 import styled from '../../src/styled'
 
 expect.addSnapshotSerializer(serializer)
@@ -14,34 +12,18 @@ expect.extend(matcher)
 
 describe('styled', () => {
   test('no dynamic', () => {
-    const H1 = styled.h1`
-      font-size: 12px;
-    `
+    const H1 = styled.h1`font-size: 12px;`
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
 
   test('basic render', () => {
     const fontSize = 20
-    const H1 = styled.h1`
-      font-size: ${fontSize}px;
-    `
+    const H1 = styled.h1`font-size: ${fontSize}px;`
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
@@ -53,13 +35,7 @@ describe('styled', () => {
       font-size: ${fontSize}px;
     `
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
@@ -90,11 +66,7 @@ describe('styled', () => {
     `
 
     const tree = renderer
-      .create(
-        <H1 className={'legacy__class'}>
-          hello world
-        </H1>
-      )
+      .create(<H1 className={'legacy__class'}>hello world</H1>)
       .toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
@@ -109,11 +81,7 @@ describe('styled', () => {
     const H2 = styled(H1)`font-size: ${fontSize * 2 / 3}`
 
     const tree = renderer
-      .create(
-        <H2 className={'legacy__class'}>
-          hello world
-        </H2>
-      )
+      .create(<H2 className={'legacy__class'}>hello world</H2>)
       .toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
@@ -198,6 +166,13 @@ describe('styled', () => {
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
+  test('injectGlobal', () => {
+    injectGlobal`
+      html {
+        background: pink;
+      }
+    `
+  })
   test('writes the correct css', () => {
     const filenameArr = basename(__filename).split('.')
     filenameArr.pop()
diff --git a/test/font-face.test.js b/test/font-face.test.js
index 134a2dac..e6ad8ea5 100644
--- a/test/font-face.test.js
+++ b/test/font-face.test.js
@@ -1,18 +1,8 @@
-/* eslint-disable jsx-quotes,no-useless-escape,no-template-curly-in-string */
 /* eslint-env jest */
-import React from 'react'
-import renderer from 'react-test-renderer'
-import { matcher, serializer } from '../jest-utils'
+import { sheet, fontFace } from '../src/index'
 
-// eslint-disable-next-line no-unused-vars
-import css, { sheet, fragment, fontFace } from '../src/index'
-
-expect.addSnapshotSerializer(serializer)
-expect.extend(matcher)
-
-// This does nothing
-describe.skip('font-face', () => {
-  test('adds font-face to sheet', done => {
+describe('font-face', () => {
+  test('adds font-face to sheet', () => {
     fontFace`
       font-family: 'Patrick Hand SC';
       font-style: normal;
@@ -20,7 +10,21 @@ describe.skip('font-face', () => {
       src: local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');
       unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
     `
-    // console.log(sheet.rules())
-    expect(sheet).toMatchSnapshot()
+    expect(
+      sheet.tags.map(tag => tag.textContent || '').join('')
+    ).toMatchSnapshot()
+  })
+  test('adds font-face to sheet with interpolation', () => {
+    const fontFamilyName = 'MyHelvetica'
+    fontFace`
+      font-family: ${fontFamilyName};
+      src: local("Helvetica Neue Bold"),
+          local("HelveticaNeue-Bold"),
+          url(MgOpenModernaBold.ttf);
+      font-weight: bold;
+    `
+    expect(
+      sheet.tags.map(tag => tag.textContent || '').join('')
+    ).toMatchSnapshot()
   })
 })
diff --git a/test/inject-global.test.js b/test/inject-global.test.js
new file mode 100644
index 00000000..3aa459f6
--- /dev/null
+++ b/test/inject-global.test.js
@@ -0,0 +1,24 @@
+/* eslint-env jest */
+import { injectGlobal, sheet, fragment } from '../src/index'
+
+test('injectGlobal', () => {
+  injectGlobal`
+    html {
+      background: pink;
+    }
+  `
+  const bodyStyles = fragment`
+    margin: 0;
+    padding: 0;
+  `
+  const color = 'yellow'
+  injectGlobal`
+    body {
+      color: ${color}
+      @apply ${bodyStyles}
+    }
+  `
+  expect(
+    sheet.tags.map(tag => tag.textContent || '').join('')
+  ).toMatchSnapshot()
+})
diff --git a/test/keyframes.test.js b/test/keyframes.test.js
index bbfa41bb..547fb81c 100644
--- a/test/keyframes.test.js
+++ b/test/keyframes.test.js
@@ -1,11 +1,8 @@
-/* eslint-disable jsx-quotes,no-useless-escape,no-template-curly-in-string */
 /* eslint-env jest */
 import React from 'react'
 import renderer from 'react-test-renderer'
 import { matcher, serializer } from '../jest-utils'
-
-// eslint-disable-next-line no-unused-vars
-import css, { fragment, keyframes } from '../src/index'
+import { keyframes, sheet } from '../src/index'
 import styled from '../src/styled'
 
 expect.addSnapshotSerializer(serializer)
@@ -40,14 +37,23 @@ describe('keyframes', () => {
       animation: ${bounce} 2s linear infinite;
     `
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
+  test('keyframes with interpolation', () => {
+    const endingRotation = '360deg'
+    keyframes`
+      from {
+        transform: rotate(0deg);
+      }
+    
+      to {
+        transform: rotate(${endingRotation});
+      }
+    `
+    expect(
+      sheet.tags.map(tag => tag.textContent || '').join('')
+    ).toMatchSnapshot()
+  })
 })
diff --git a/test/styled.test.js b/test/styled.test.js
index 3c9c4c19..fce76e52 100644
--- a/test/styled.test.js
+++ b/test/styled.test.js
@@ -13,34 +13,18 @@ expect.extend(matcher)
 
 describe('styled', () => {
   test('no dynamic', () => {
-    const H1 = styled.h1`
-      font-size: 12px;
-    `
+    const H1 = styled.h1`font-size: 12px;`
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
 
   test('basic render', () => {
     const fontSize = 20
-    const H1 = styled.h1`
-      font-size: ${fontSize}px;
-    `
+    const H1 = styled.h1`font-size: ${fontSize}px;`
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
@@ -52,13 +36,7 @@ describe('styled', () => {
       font-size: ${fontSize}px;
     `
 
-    const tree = renderer
-      .create(
-        <H1>
-          hello world
-        </H1>
-      )
-      .toJSON()
+    const tree = renderer.create(<H1>hello world</H1>).toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
@@ -89,11 +67,7 @@ describe('styled', () => {
     `
 
     const tree = renderer
-      .create(
-        <H1 className={'legacy__class'}>
-          hello world
-        </H1>
-      )
+      .create(<H1 className={'legacy__class'}>hello world</H1>)
       .toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
@@ -108,11 +82,7 @@ describe('styled', () => {
     const H2 = styled(H1)`font-size: ${fontSize * 2 / 3}`
 
     const tree = renderer
-      .create(
-        <H2 className={'legacy__class'}>
-          hello world
-        </H2>
-      )
+      .create(<H2 className={'legacy__class'}>hello world</H2>)
       .toJSON()
 
     expect(tree).toMatchSnapshotWithEmotion()
@@ -197,4 +167,9 @@ describe('styled', () => {
 
     expect(tree).toMatchSnapshotWithEmotion()
   })
+  test('throws if undefined is passed as the component', () => {
+    expect(
+      () => styled(undefined)`display: flex;`
+    ).toThrowErrorMatchingSnapshot()
+  })
 })