Repository: tailwindlabs/tailwindcss
Pull request #5517 (2021-09-16 10:36:13)

Improve experimental universal selector improvements


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ffd56f2..27c43c0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Add `scroll-behavior` utilities ([#5388](https://github.com/tailwindlabs/tailwindcss/pull/5388))
 - Add `border-hidden` utility ([#5485](https://github.com/tailwindlabs/tailwindcss/pull/5485))
 - Add `align-sub` and `align-super` utilities by default ([#5486](https://github.com/tailwindlabs/tailwindcss/pull/5486))
+- Add `break-before`, `break-inside` and `break-after` utilities ([#5530](https://github.com/tailwindlabs/tailwindcss/pull/5530))
 
 ### Fixed
 
diff --git a/defaultConfig.js b/defaultConfig.js
index 0a131abc..fb691d3a 100644
--- a/defaultConfig.js
+++ b/defaultConfig.js
@@ -1,4 +1,4 @@
-let { cloneDeep } = require('./src/util/cloneDeep')
+let { cloneDeep } = require('./lib/util/cloneDeep')
 let defaultConfig = require('./stubs/defaultConfig.stub.js')
 
 module.exports = cloneDeep(defaultConfig)
diff --git a/defaultTheme.js b/defaultTheme.js
index 4a42a998..ee7282c1 100644
--- a/defaultTheme.js
+++ b/defaultTheme.js
@@ -1,4 +1,4 @@
-let { cloneDeep } = require('./src/util/cloneDeep')
+let { cloneDeep } = require('./lib/util/cloneDeep')
 let defaultConfig = require('./stubs/defaultConfig.stub.js')
 
 module.exports = cloneDeep(defaultConfig.theme)
diff --git a/package-lock.json b/package-lock.json
index bc23bb5d..8c13592e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,7 +41,7 @@
         "@babel/node": "^7.12.13",
         "@babel/preset-env": "^7.15.0",
         "@tailwindcss/aspect-ratio": "^0.2.1",
-        "@vercel/ncc": "^0.29.2",
+        "@vercel/ncc": "^0.31.1",
         "autoprefixer": "^10.3.3",
         "babel-jest": "^27.1.0",
         "cross-env": "^7.0.3",
@@ -53,7 +53,7 @@
         "jest-diff": "^27.1.0",
         "postcss": "^8.3.6",
         "postcss-cli": "^8.3.1",
-        "prettier": "^2.3.2",
+        "prettier": "^2.4.1",
         "rimraf": "^3.0.0"
       },
       "engines": {
@@ -2492,9 +2492,9 @@
       "dev": true
     },
     "node_modules/@vercel/ncc": {
-      "version": "0.29.2",
-      "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.29.2.tgz",
-      "integrity": "sha512-eUxibZD92k+rY0oZJFZooGqdVpGkDrrHlUhj9UAWrtoyXCGmNOWC1Kcr2KPrZoHRSlWwHOcRnkn2nGT+aHY2KA==",
+      "version": "0.31.1",
+      "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.31.1.tgz",
+      "integrity": "sha512-g0FAxwdViI6UzsiVz5HssIHqjcPa1EHL6h+2dcJD893SoCJaGdqqgUF09xnMW6goWnnhbLvgiKlgJWrJa+7qYA==",
       "dev": true,
       "bin": {
         "ncc": "dist/ncc/cli.js"
@@ -8533,9 +8533,9 @@
       }
     },
     "node_modules/prettier": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
-      "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
+      "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
       "dev": true,
       "bin": {
         "prettier": "bin-prettier.js"
@@ -12144,9 +12144,9 @@
       "dev": true
     },
     "@vercel/ncc": {
-      "version": "0.29.2",
-      "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.29.2.tgz",
-      "integrity": "sha512-eUxibZD92k+rY0oZJFZooGqdVpGkDrrHlUhj9UAWrtoyXCGmNOWC1Kcr2KPrZoHRSlWwHOcRnkn2nGT+aHY2KA==",
+      "version": "0.31.1",
+      "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.31.1.tgz",
+      "integrity": "sha512-g0FAxwdViI6UzsiVz5HssIHqjcPa1EHL6h+2dcJD893SoCJaGdqqgUF09xnMW6goWnnhbLvgiKlgJWrJa+7qYA==",
       "dev": true
     },
     "abab": {
@@ -16671,9 +16671,9 @@
       "dev": true
     },
     "prettier": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
-      "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
+      "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
       "dev": true
     },
     "prettier-linter-helpers": {
diff --git a/package.json b/package.json
index 8ecad63d..aa8f20c9 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
     "@babel/node": "^7.12.13",
     "@babel/preset-env": "^7.15.0",
     "@tailwindcss/aspect-ratio": "^0.2.1",
-    "@vercel/ncc": "^0.29.2",
+    "@vercel/ncc": "^0.31.1",
     "autoprefixer": "^10.3.3",
     "babel-jest": "^27.1.0",
     "cross-env": "^7.0.3",
@@ -59,7 +59,7 @@
     "jest-diff": "^27.1.0",
     "postcss": "^8.3.6",
     "postcss-cli": "^8.3.1",
-    "prettier": "^2.3.2",
+    "prettier": "^2.4.1",
     "rimraf": "^3.0.0"
   },
   "peerDependencies": {
diff --git a/src/corePlugins.js b/src/corePlugins.js
index 80a610ab..e6026dda 100644
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -813,6 +813,41 @@ export let appearance = ({ addUtilities }) => {
 
 export let columns = createUtilityPlugin('columns', [['columns', ['columns']]])
 
+export let breakBefore = ({ addUtilities }) => {
+  addUtilities({
+    '.break-before-auto': { 'break-before': 'auto' },
+    '.break-before-avoid': { 'break-before': 'avoid' },
+    '.break-before-all': { 'break-before': 'all' },
+    '.break-before-avoid-page': { 'break-before': 'avoid-page' },
+    '.break-before-page': { 'break-before': 'page' },
+    '.break-before-left': { 'break-before': 'left' },
+    '.break-before-right': { 'break-before': 'right' },
+    '.break-before-column': { 'break-before': 'column' },
+  })
+}
+
+export let breakInside = ({ addUtilities }) => {
+  addUtilities({
+    '.break-inside-auto': { 'break-inside': 'auto' },
+    '.break-inside-avoid': { 'break-inside': 'avoid' },
+    '.break-inside-avoid-page': { 'break-inside': 'avoid-page' },
+    '.break-inside-avoid-column': { 'break-inside': 'avoid-column' },
+  })
+}
+
+export let breakAfter = ({ addUtilities }) => {
+  addUtilities({
+    '.break-after-auto': { 'break-after': 'auto' },
+    '.break-after-avoid': { 'break-after': 'avoid' },
+    '.break-after-all': { 'break-after': 'all' },
+    '.break-after-avoid-page': { 'break-after': 'avoid-page' },
+    '.break-after-page': { 'break-after': 'page' },
+    '.break-after-left': { 'break-after': 'left' },
+    '.break-after-right': { 'break-after': 'right' },
+    '.break-after-column': { 'break-after': 'column' },
+  })
+}
+
 export let gridAutoColumns = createUtilityPlugin('gridAutoColumns', [
   ['auto-cols', ['gridAutoColumns']],
 ])
diff --git a/src/lib/resolveDefaultsAtRules.js b/src/lib/resolveDefaultsAtRules.js
index 95887b35..e8c1399b 100644
--- a/src/lib/resolveDefaultsAtRules.js
+++ b/src/lib/resolveDefaultsAtRules.js
@@ -3,7 +3,20 @@ import selectorParser from 'postcss-selector-parser'
 import { flagEnabled } from '../featureFlags'
 
 function minimumImpactSelector(nodes) {
-  let pseudos = nodes.filter((n) => n.type === 'pseudo')
+  let rest = nodes
+    // Keep all pseudo & combinator types (:not([hidden]) ~ :not([hidden]))
+    .filter((n) => n.type === 'pseudo' || n.type === 'combinator')
+    // Remove leading pseudo's (:hover, :focus, ...)
+    .filter((n, idx, all) => {
+      // Keep pseudo elements
+      if (n.type === 'pseudo' && n.value.startsWith('::')) return true
+
+      if (idx === 0 && n.type === 'pseudo') return false
+      if (idx > 0 && n.type === 'pseudo' && all[idx - 1].type === 'pseudo') return false
+
+      return true
+    })
+
   let [bestNode] = nodes
 
   for (let [type, getNode = (n) => n] of [
@@ -28,16 +41,12 @@ function minimumImpactSelector(nodes) {
     }
   }
 
-  return [bestNode, ...pseudos].join('').trim()
+  return [bestNode, ...rest].join('').trim()
 }
 
 let elementSelectorParser = selectorParser((selectors) => {
   return selectors.map((s) => {
-    let nodes = s
-      .split((n) => n.type === 'combinator')
-      .pop()
-      .filter((n) => n.type !== 'pseudo' || n.value.startsWith('::'))
-
+    let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
     return minimumImpactSelector(nodes)
   })
 })


Test patch:
diff --git a/tests/basic-usage.test.css b/tests/basic-usage.test.css
index 86f5da88..00bb9e41 100644
--- a/tests/basic-usage.test.css
+++ b/tests/basic-usage.test.css
@@ -320,6 +320,15 @@
 .columns-md {
   columns: 28rem;
 }
+.break-before-page  {
+  break-before: page;
+}
+.break-inside-avoid-column  {
+  break-inside: avoid-column;
+}
+.break-after-auto {
+  break-after: auto;
+}
 .auto-cols-min {
   grid-auto-columns: min-content;
 }
diff --git a/tests/basic-usage.test.html b/tests/basic-usage.test.html
index 539fc1a5..01103fc3 100644
--- a/tests/basic-usage.test.html
+++ b/tests/basic-usage.test.html
@@ -62,6 +62,7 @@
     <div class="gap-x-2 gap-y-3 gap-4"></div>
     <div class="from-red-300 via-purple-200 to-blue-400"></div>
     <div class="columns-1 columns-md"></div>
+    <div class="break-before-page break-inside-avoid-column break-after-auto"></div>
     <div class="auto-cols-min"></div>
     <div class="grid-flow-row"></div>
     <div class="auto-rows-max"></div>
diff --git a/tests/experimental.test.js b/tests/experimental.test.js
new file mode 100644
index 00000000..6404c001
--- /dev/null
+++ b/tests/experimental.test.js
@@ -0,0 +1,209 @@
+import { run, html, css } from './util/run'
+
+test('experimental universal selector improvements (box-shadow)', () => {
+  let config = {
+    experimental: 'all',
+    content: [{ raw: html`<div class="shadow resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .shadow {
+        --tw-ring-offset-shadow: 0 0 #0000;
+        --tw-ring-shadow: 0 0 #0000;
+        --tw-shadow: 0 0 #0000;
+      }
+
+      .resize {
+        resize: both;
+      }
+
+      .shadow {
+        --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
+        box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+          var(--tw-shadow);
+      }
+    `)
+  })
+})
+
+test('experimental universal selector improvements (pseudo hover)', () => {
+  let config = {
+    experimental: 'all',
+    content: [{ raw: html`<div class="hover:shadow resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .hover\\:shadow {
+        --tw-ring-offset-shadow: 0 0 #0000;
+        --tw-ring-shadow: 0 0 #0000;
+        --tw-shadow: 0 0 #0000;
+      }
+
+      .resize {
+        resize: both;
+      }
+
+      .hover\\:shadow:hover {
+        --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
+        box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+          var(--tw-shadow);
+      }
+    `)
+  })
+})
+
+test('experimental universal selector improvements (multiple classes: group)', () => {
+  let config = {
+    experimental: 'all',
+    content: [{ raw: html`<div class="group-hover:shadow resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .group-hover\\:shadow {
+        --tw-ring-offset-shadow: 0 0 #0000;
+        --tw-ring-shadow: 0 0 #0000;
+        --tw-shadow: 0 0 #0000;
+      }
+
+      .resize {
+        resize: both;
+      }
+
+      .group:hover .group-hover\\:shadow {
+        --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
+        box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+          var(--tw-shadow);
+      }
+    `)
+  })
+})
+
+test('experimental universal selector improvements (child selectors: divide-y)', () => {
+  let config = {
+    experimental: 'all',
+    content: [{ raw: html`<div class="divide-y resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-y > :not([hidden]) ~ :not([hidden]) {
+        --tw-border-opacity: 1;
+        border-color: rgb(229 231 235 / var(--tw-border-opacity));
+      }
+
+      .resize {
+        resize: both;
+      }
+
+      .divide-y > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-y-reverse: 0;
+        border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+        border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+      }
+    `)
+  })
+})
+
+test('experimental universal selector improvements (hover:divide-y)', () => {
+  let config = {
+    experimental: 'all',
+    content: [{ raw: html`<div class="hover:divide-y resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .hover\\:divide-y > :not([hidden]) ~ :not([hidden]) {
+        --tw-border-opacity: 1;
+        border-color: rgb(229 231 235 / var(--tw-border-opacity));
+      }
+
+      .resize {
+        resize: both;
+      }
+
+      .hover\\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-y-reverse: 0;
+        border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+        border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+      }
+    `)
+  })
+})
+
+test('experimental universal selector improvements (#app important)', () => {
+  let config = {
+    experimental: 'all',
+    important: '#app',
+    content: [{ raw: html`<div class="shadow divide-y resize"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-y > :not([hidden]) ~ :not([hidden]) {
+        --tw-border-opacity: 1;
+        border-color: rgb(229 231 235 / var(--tw-border-opacity));
+      }
+
+      .shadow {
+        --tw-ring-offset-shadow: 0 0 #0000;
+        --tw-ring-shadow: 0 0 #0000;
+        --tw-shadow: 0 0 #0000;
+      }
+
+      #app .resize {
+        resize: both;
+      }
+
+      #app .divide-y > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-y-reverse: 0;
+        border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+        border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+      }
+
+      #app .shadow {
+        --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
+        box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+          var(--tw-shadow);
+      }
+    `)
+  })
+})
diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js
index 0a4d2af1..6f250bc6 100644
--- a/tests/resolve-defaults-at-rules.test.js
+++ b/tests/resolve-defaults-at-rules.test.js
@@ -527,6 +527,7 @@ test('when a utility uses defaults but they do not exist', async () => {
 
 test('selectors are reduced to the lowest possible specificity', async () => {
   let config = {
+    experimental: 'all',
     content: [{ raw: html`<div class="foo"></div>` }],
     corePlugins: [],
   }
@@ -576,9 +577,13 @@ test('selectors are reduced to the lowest possible specificity', async () => {
 
   return run(input, config).then((result) => {
     expect(result.css).toMatchFormattedCss(css`
-      *,
-      ::before,
-      ::after {
+      .foo,
+      [id='app'],
+      [id='page'],
+      [id='other'],
+      [data-bar='baz'],
+      article,
+      [id='another']::before {
         --color: black;
       }
 
diff --git a/tests/svelte-syntax.test.css b/tests/svelte-syntax.test.css
deleted file mode 100644
index 20aaf36f..00000000
--- a/tests/svelte-syntax.test.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.bg-red-500 {
-  --tw-bg-opacity: 1;
-  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
-}
-@media (min-width: 1024px) {
-  .lg\:hover\:bg-blue-500:hover {
-    --tw-bg-opacity: 1;
-    background-color: rgb(59 130 246 / var(--tw-bg-opacity));
-  }
-}
diff --git a/tests/svelte-syntax.test.js b/tests/svelte-syntax.test.js
deleted file mode 100644
index 38bf51f6..00000000
--- a/tests/svelte-syntax.test.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-
-import { run, css } from './util/run'
-
-test('basic usage', () => {
-  let config = {
-    content: [path.resolve(__dirname, './svelte-syntax.test.svelte')],
-    corePlugins: { preflight: false },
-    theme: {},
-    plugins: [],
-  }
-
-  let input = css`
-    @tailwind components;
-    @tailwind utilities;
-  `
-
-  return run(input, config).then((result) => {
-    let expectedPath = path.resolve(__dirname, './svelte-syntax.test.css')
-    let expected = fs.readFileSync(expectedPath, 'utf8')
-
-    expect(result.css).toMatchFormattedCss(expected)
-  })
-})
diff --git a/tests/syntax-lit-html.test.js b/tests/syntax-lit-html.test.js
new file mode 100644
index 00000000..dbc8936d
--- /dev/null
+++ b/tests/syntax-lit-html.test.js
@@ -0,0 +1,47 @@
+import { run } from './util/run'
+
+let css = String.raw
+
+test('it detects classes in lit-html templates', () => {
+  let config = {
+    content: [
+      {
+        raw: `html\`<button class="bg-blue-400 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">\${data.title}</button>\`;`,
+      },
+    ],
+    corePlugins: { preflight: false },
+    theme: {},
+    plugins: [],
+  }
+
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .rounded {
+        border-radius: 0.25rem;
+      }
+      .bg-blue-400 {
+        --tw-bg-opacity: 1;
+        background-color: rgb(96 165 250 / var(--tw-bg-opacity));
+      }
+      .py-2 {
+        padding-top: 0.5rem;
+        padding-bottom: 0.5rem;
+      }
+      .px-4 {
+        padding-left: 1rem;
+        padding-right: 1rem;
+      }
+      .font-bold {
+        font-weight: 700;
+      }
+      .text-white {
+        --tw-text-opacity: 1;
+        color: rgb(255 255 255 / var(--tw-text-opacity));
+      }
+      .hover\:bg-blue-600:hover {
+        --tw-bg-opacity: 1;
+        background-color: rgb(37 99 235 / var(--tw-bg-opacity));
+      }
+    `)
+  })
+})
diff --git a/tests/syntax-svelte.test.js b/tests/syntax-svelte.test.js
new file mode 100644
index 00000000..83e038d1
--- /dev/null
+++ b/tests/syntax-svelte.test.js
@@ -0,0 +1,80 @@
+import path from 'path'
+
+import { run } from './util/run'
+
+let css = String.raw
+
+test('it detects svelte based on the file extension', () => {
+  let config = {
+    content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
+    corePlugins: { preflight: false },
+    theme: {},
+    plugins: [],
+  }
+
+  let input = css`
+    @tailwind components;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .bg-red-500 {
+        --tw-bg-opacity: 1;
+        background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+      }
+      @media (min-width: 1024px) {
+        .lg\:hover\:bg-blue-500:hover {
+          --tw-bg-opacity: 1;
+          background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+        }
+      }
+    `)
+  })
+})
+
+test('using raw with svelte extension', () => {
+  let config = {
+    content: [
+      {
+        raw: `
+        <script>
+          let current = 'foo'
+        </script>
+
+        <button class:lg:hover:bg-blue-500={current === 'foo'}>Click me</button>
+
+        <button
+          class:bg-red-500={current === 'foo'}
+        >
+          Click me
+        </button>
+        `,
+        extension: 'svelte',
+      },
+    ],
+    corePlugins: { preflight: false },
+    theme: {},
+    plugins: [],
+  }
+
+  let input = css`
+    @tailwind components;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .bg-red-500 {
+        --tw-bg-opacity: 1;
+        background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+      }
+      @media (min-width: 1024px) {
+        .lg\:hover\:bg-blue-500:hover {
+          --tw-bg-opacity: 1;
+          background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+        }
+      }
+    `)
+  })
+})