Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
group-*:animate-* generates invalid keyframe definition
### What version of Tailwind CSS are you using?

v2.2.0

### What build tool (or framework if it abstracts the build tool) are you using?

postcss@7.0.36, webpack@5.39.1

### What version of Node.js are you using?

v15.10.0

### What browser are you using?

Chrome

### What operating system are you using?

macOS

### Reproduction repository

https://play.tailwindcss.com/DrsR2rdrZO

### Describe your issue

Adding an animation that should only be applied to group children (e.g. group-focus group-hover) generates invalid keyframes (offsets are missing):
```css
@keyframes bounce {

	, {
		-webkit-transform: translateY(-25%);
		        transform: translateY(-25%);
		-webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);
		        animation-timing-function: cubic-bezier(0.8,0,1,1);
	}

	 {
		-webkit-transform: none;
		        transform: none;
		-webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);
		        animation-timing-function: cubic-bezier(0,0,0.2,1);
	}
}
.group:focus .group-focus\:animate-bounce {
	-webkit-animation: bounce 1s infinite;
	        animation: bounce 1s infinite;
}
```

expected (taken from non-group animate-* classes):
```css
@keyframes bounce {

	0%, 100% {
		-webkit-transform: translateY(-25%);
		        transform: translateY(-25%);
		-webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);
		        animation-timing-function: cubic-bezier(0.8,0,1,1);
	}

	50% {
		-webkit-transform: none;
		        transform: none;
		-webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);
		        animation-timing-function: cubic-bezier(0,0,0.2,1);
	}
}
.animate-bounce {
	-webkit-animation: bounce 1s infinite;
	        animation: bounce 1s infinite;
}
```


Hints:
Found this in my search, I hope it helps:
https://github.com/tailwindlabs/play.tailwindcss.com/issues/54

If you downgrade to 2.1.4 it should work again.
Yeah it worked before, so definitely a regression.
I stepped trough the code a little and it looks like the `{ respectVariants: false }` config gets lost somewhere and is not set in https://github.com/tailwindlabs/tailwindcss/blob/0d47ffd2d38552f3a1ba702f5ff2818da0fb7418/src/jit/lib/generateRules.js#L97 so the value gets handled as if it was a css selector.
Also experiencing the same issue, and since downgrading currently isn't an option for me I'd like to see this fixed.
My team is currently running into this issue as well. Looking forward to the fix in the next version
Also running into this issue, currently downgraded to version `2.1.4`
Same problem for me.
Same problem for me.

Patch:
diff --git a/src/plugins/animation.js b/src/plugins/animation.js
--- a/src/plugins/animation.js
+++ b/src/plugins/animation.js
@@ -11,7 +11,6 @@ export default function () {
             {
               [`@keyframes ${prefixName(key)}`]: value,
             },
-            { respectVariants: false },
           ],
         ]
       })
diff --git a/src/util/isKeyframeRule.js b/src/util/isKeyframeRule.js
new file mode 100644
--- /dev/null
+++ b/src/util/isKeyframeRule.js
@@ -0,0 +1,3 @@
+export default function isKeyframeRule(rule) {
+  return rule.parent && rule.parent.type === 'atrule' && /keyframes$/.test(rule.parent.name)
+}
diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js
--- a/src/util/pluginUtils.js
+++ b/src/util/pluginUtils.js
@@ -3,6 +3,7 @@ import postcss from 'postcss'
 import createColor from 'color'
 import escapeCommas from './escapeCommas'
 import { withAlphaValue } from './withAlphaVariable'
+import isKeyframeRule from './isKeyframeRule'
 
 export function applyPseudoToMarker(selector, marker, state, join) {
   let states = [state]
@@ -72,6 +73,9 @@ export function updateLastClasses(selectors, updateClass) {
 export function transformAllSelectors(transformSelector, { wrap, withRule } = {}) {
   return ({ container }) => {
     container.walkRules((rule) => {
+      if (isKeyframeRule(rule)) {
+        return rule
+      }
       let transformed = rule.selector.split(',').map(transformSelector).join(',')
       rule.selector = transformed
       if (withRule) {
diff --git a/src/util/processPlugins.js b/src/util/processPlugins.js
--- a/src/util/processPlugins.js
+++ b/src/util/processPlugins.js
@@ -10,6 +10,7 @@ import wrapWithVariants from '../util/wrapWithVariants'
 import cloneNodes from '../util/cloneNodes'
 import transformThemeValue from './transformThemeValue'
 import nameClass from '../util/nameClass'
+import isKeyframeRule from '../util/isKeyframeRule'
 
 function parseStyles(styles) {
   if (!Array.isArray(styles)) {
@@ -28,10 +29,6 @@ function wrapWithLayer(rules, layer) {
     .append(cloneNodes(Array.isArray(rules) ? rules : [rules]))
 }
 
-function isKeyframeRule(rule) {
-  return rule.parent && rule.parent.type === 'atrule' && /keyframes$/.test(rule.parent.name)
-}
-
 export default function (plugins, config) {
   const pluginBaseStyles = []
   const pluginComponents = []


Test patch:
diff --git a/tests/jit/animations.test.css b/tests/jit/animations.test.css
new file mode 100644
--- /dev/null
+++ b/tests/jit/animations.test.css
@@ -0,0 +1,32 @@
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+@keyframes ping {
+  75%,
+  100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+.hover\:animate-ping:hover {
+  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+}
+@keyframes bounce {
+  0%,
+  100% {
+    transform: translateY(-25%);
+    animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
+  }
+  50% {
+    transform: none;
+    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+  }
+}
+.group:hover .group-hover\:animate-bounce {
+  animation: bounce 1s infinite;
+}
diff --git a/tests/jit/animations.test.html b/tests/jit/animations.test.html
new file mode 100644
--- /dev/null
+++ b/tests/jit/animations.test.html
@@ -0,0 +1,3 @@
+<div class="animate-spin"></div>
+<div class="hover:animate-ping"></div>
+<div class="group-hover:animate-bounce"></div>
diff --git a/tests/jit/animations.test.js b/tests/jit/animations.test.js
new file mode 100644
--- /dev/null
+++ b/tests/jit/animations.test.js
@@ -0,0 +1,30 @@
+import postcss from 'postcss'
+import fs from 'fs'
+import path from 'path'
+import tailwind from '../../src/jit/index.js'
+
+function run(input, config = {}) {
+  return postcss(tailwind(config)).process(input, {
+    from: path.resolve(__filename),
+  })
+}
+
+test('animations', () => {
+  let config = {
+    darkMode: 'class',
+    mode: 'jit',
+    purge: [path.resolve(__dirname, './animations.test.html')],
+    corePlugins: {},
+    theme: {},
+    plugins: [],
+  }
+
+  let css = `@tailwind utilities`
+
+  return run(css, config).then((result) => {
+    let expectedPath = path.resolve(__dirname, './animations.test.css')
+    let expected = fs.readFileSync(expectedPath, 'utf8')
+
+    expect(result.css).toMatchFormattedCss(expected)
+  })
+})