basic-usage.test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. import { run, html, css, defaults } from './util/run'
  2. import { flagEnabled } from '../src/featureFlags'
  3. test('all plugins are executed that match a candidate', () => {
  4. let config = {
  5. content: [{ raw: html`<div class="bg-green-light bg-green"></div>` }],
  6. theme: {
  7. colors: {
  8. green: {
  9. light: 'green',
  10. },
  11. },
  12. },
  13. corePlugins: { preflight: false },
  14. }
  15. let input = css`
  16. @tailwind utilities;
  17. .bg-green {
  18. /* Empty on purpose */
  19. }
  20. `
  21. return run(input, config).then((result) => {
  22. expect(result.css).toMatchFormattedCss(css`
  23. .bg-green-light {
  24. --tw-bg-opacity: 1;
  25. background-color: rgb(0 128 0 / var(--tw-bg-opacity));
  26. }
  27. `)
  28. })
  29. })
  30. test('per-plugin colors with the same key can differ when using a custom colors object', () => {
  31. let config = {
  32. content: [
  33. {
  34. raw: html`
  35. <div class="bg-theme text-theme">This should be green text on red background.</div>
  36. `,
  37. },
  38. ],
  39. theme: {
  40. // colors & theme MUST be plain objects
  41. // If they're functions here the test passes regardless
  42. colors: {
  43. theme: {
  44. bg: 'red',
  45. text: 'green',
  46. },
  47. },
  48. extend: {
  49. textColor: {
  50. theme: {
  51. DEFAULT: 'green',
  52. },
  53. },
  54. backgroundColor: {
  55. theme: {
  56. DEFAULT: 'red',
  57. },
  58. },
  59. },
  60. },
  61. corePlugins: { preflight: false },
  62. }
  63. let input = css`
  64. @tailwind utilities;
  65. `
  66. return run(input, config).then((result) => {
  67. expect(result.css).toMatchFormattedCss(css`
  68. .bg-theme {
  69. --tw-bg-opacity: 1;
  70. background-color: rgb(255 0 0 / var(--tw-bg-opacity));
  71. }
  72. .text-theme {
  73. --tw-text-opacity: 1;
  74. color: rgb(0 128 0 / var(--tw-text-opacity));
  75. }
  76. `)
  77. })
  78. })
  79. test('default ring color can be a function', () => {
  80. function color(variable) {
  81. return function ({ opacityVariable, opacityValue }) {
  82. if (opacityValue !== undefined) {
  83. return `rgba(${variable}, ${opacityValue})`
  84. }
  85. if (opacityVariable !== undefined) {
  86. return `rgba(${variable}, var(${opacityVariable}, 1))`
  87. }
  88. return `rgb(${variable})`
  89. }
  90. }
  91. let config = {
  92. content: [
  93. {
  94. raw: html` <div class="ring"></div> `,
  95. },
  96. ],
  97. theme: {
  98. extend: {
  99. ringColor: {
  100. DEFAULT: color('var(--red)'),
  101. },
  102. },
  103. },
  104. plugins: [],
  105. corePlugins: { preflight: false },
  106. }
  107. let input = css`
  108. @tailwind base;
  109. @tailwind components;
  110. @tailwind utilities;
  111. `
  112. return run(input, config).then((result) => {
  113. expect(result.css).toMatchFormattedCss(css`
  114. ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
  115. .ring {
  116. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  117. var(--tw-ring-offset-color);
  118. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  119. var(--tw-ring-color);
  120. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  121. }
  122. `)
  123. })
  124. })
  125. it('falsy config values still work', () => {
  126. let config = {
  127. content: [{ raw: html`<div class="inset-0"></div>` }],
  128. theme: {
  129. inset: {
  130. 0: 0,
  131. },
  132. },
  133. plugins: [],
  134. corePlugins: { preflight: false },
  135. }
  136. let input = css`
  137. @tailwind utilities;
  138. `
  139. return run(input, config).then((result) => {
  140. expect(result.css).toMatchFormattedCss(css`
  141. .inset-0 {
  142. inset: 0;
  143. }
  144. `)
  145. })
  146. })
  147. it('shadows support values without a leading zero', () => {
  148. let config = {
  149. content: [{ raw: html`<div class="shadow-one shadow-two"></div>` }],
  150. theme: {
  151. boxShadow: {
  152. one: '0.5rem 0.5rem 0.5rem #0005',
  153. two: '.5rem .5rem .5rem #0005',
  154. },
  155. },
  156. plugins: [],
  157. corePlugins: { preflight: false },
  158. }
  159. let input = css`
  160. @tailwind utilities;
  161. `
  162. return run(input, config).then((result) => {
  163. expect(result.css).toMatchFormattedCss(css`
  164. .shadow-one,
  165. .shadow-two {
  166. --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
  167. --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
  168. box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
  169. var(--tw-shadow);
  170. }
  171. `)
  172. })
  173. })
  174. it('can scan extremely long classes without crashing', () => {
  175. let val = 'cols-' + '-a'.repeat(65536)
  176. let config = {
  177. content: [{ raw: html`<div class="${val}"></div>` }],
  178. corePlugins: { preflight: false },
  179. }
  180. let input = css`
  181. @tailwind utilities;
  182. `
  183. return run(input, config).then((result) => {
  184. expect(result.css).toMatchFormattedCss(css``)
  185. })
  186. })
  187. it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
  188. let config = {
  189. content: [{ raw: html`underline focus:*` }],
  190. corePlugins: { preflight: false },
  191. }
  192. let input = css`
  193. @tailwind base;
  194. @tailwind components;
  195. @tailwind utilities;
  196. * {
  197. color: red;
  198. }
  199. .combined,
  200. * {
  201. text-align: center;
  202. }
  203. @layer base {
  204. * {
  205. color: blue;
  206. }
  207. .combined,
  208. * {
  209. color: red;
  210. }
  211. }
  212. `
  213. return run(input, config).then((result) => {
  214. expect(result.css).toMatchFormattedCss(css`
  215. .combined,
  216. * {
  217. color: red;
  218. }
  219. ${defaults}
  220. .underline {
  221. text-decoration-line: underline;
  222. }
  223. * {
  224. color: red;
  225. }
  226. .combined,
  227. * {
  228. text-align: center;
  229. }
  230. `)
  231. })
  232. })
  233. it('can parse box shadows with variables', () => {
  234. let config = {
  235. content: [{ raw: html`<div class="shadow-lg"></div>` }],
  236. theme: {
  237. boxShadow: {
  238. lg: 'var(--a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
  239. },
  240. },
  241. corePlugins: { preflight: false },
  242. }
  243. let input = css`
  244. @tailwind utilities;
  245. `
  246. return run(input, config).then((result) => {
  247. expect(result.css).toMatchFormattedCss(css`
  248. .shadow-lg {
  249. --tw-shadow: var(--a, 0 35px 60px -15px #000), 0 0 1px #000;
  250. --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
  251. 0 0 1px var(--tw-shadow-color);
  252. box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
  253. var(--tw-shadow);
  254. }
  255. `)
  256. })
  257. })
  258. it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
  259. let config = {
  260. content: [{ raw: html`<div></div>` }],
  261. corePlugins: { preflight: false },
  262. }
  263. let input = css`
  264. @tailwind components;
  265. @layer components {
  266. div:not(.unknown-class) {
  267. color: red;
  268. }
  269. }
  270. `
  271. return run(input, config).then((result) => {
  272. expect(result.css).toMatchFormattedCss(css`
  273. div:not(.unknown-class) {
  274. color: red;
  275. }
  276. `)
  277. })
  278. })
  279. it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
  280. let config = {
  281. content: [
  282. {
  283. raw: html`<div
  284. class="bg-[url('/images/one-two-three.png'),linear-gradient(to_right,_#eeeeee,_#000000)]"
  285. ></div>`,
  286. },
  287. ],
  288. corePlugins: { preflight: false },
  289. }
  290. let input = css`
  291. @tailwind utilities;
  292. `
  293. return run(input, config).then((result) => {
  294. expect(result.css).toMatchFormattedCss(css`
  295. .bg-\[url\(\'\/images\/one-two-three\.png\'\)\,linear-gradient\(to_right\,_\#eeeeee\,_\#000000\)\] {
  296. background-image: url('/images/one-two-three.png'), linear-gradient(to right, #eee, #000);
  297. }
  298. `)
  299. })
  300. })
  301. it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
  302. let config = {
  303. content: [{ raw: html`<div class="ring"></div>` }],
  304. corePlugins: { preflight: false },
  305. }
  306. let input = css`
  307. @tailwind base;
  308. @tailwind utilities;
  309. `
  310. return run(input, config).then((result) => {
  311. expect(result.css).toMatchFormattedCss(css`
  312. ${defaults({ defaultRingColor: '#3b82f680' })}
  313. .ring {
  314. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  315. var(--tw-ring-offset-color);
  316. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  317. var(--tw-ring-color);
  318. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  319. }
  320. `)
  321. })
  322. })
  323. it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
  324. let config = {
  325. content: [{ raw: html`<div class="ring"></div>` }],
  326. corePlugins: { preflight: false },
  327. theme: {
  328. ringOpacity: {
  329. DEFAULT: 0.75,
  330. },
  331. },
  332. }
  333. let input = css`
  334. @tailwind base;
  335. @tailwind utilities;
  336. `
  337. return run(input, config).then((result) => {
  338. expect(result.css).toMatchFormattedCss(css`
  339. ${defaults({ defaultRingColor: '#3b82f6bf' })}
  340. .ring {
  341. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  342. var(--tw-ring-offset-color);
  343. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  344. var(--tw-ring-color);
  345. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  346. }
  347. `)
  348. })
  349. })
  350. it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
  351. let config = {
  352. content: [{ raw: html`<div class="ring"></div>` }],
  353. corePlugins: { preflight: false },
  354. theme: {
  355. ringColor: {
  356. DEFAULT: '#ff7f7f',
  357. },
  358. },
  359. }
  360. let input = css`
  361. @tailwind base;
  362. @tailwind utilities;
  363. `
  364. return run(input, config).then((result) => {
  365. expect(result.css).toMatchFormattedCss(css`
  366. ${defaults({ defaultRingColor: '#ff7f7f80' })}
  367. .ring {
  368. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  369. var(--tw-ring-offset-color);
  370. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  371. var(--tw-ring-color);
  372. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  373. }
  374. `)
  375. })
  376. })
  377. it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
  378. let config = {
  379. content: [{ raw: html`<div class="ring"></div>` }],
  380. corePlugins: { preflight: false },
  381. theme: {
  382. ringColor: {
  383. DEFAULT: '#ff7f7f00',
  384. },
  385. },
  386. }
  387. let input = css`
  388. @tailwind base;
  389. @tailwind utilities;
  390. `
  391. return run(input, config).then((result) => {
  392. expect(result.css).toMatchFormattedCss(css`
  393. ${defaults({ defaultRingColor: '#ff7f7f80' })}
  394. .ring {
  395. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  396. var(--tw-ring-offset-color);
  397. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  398. var(--tw-ring-color);
  399. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  400. }
  401. `)
  402. })
  403. })
  404. it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
  405. let config = {
  406. future: { respectDefaultRingColorOpacity: true },
  407. content: [{ raw: html`<div class="ring"></div>` }],
  408. corePlugins: { preflight: false },
  409. }
  410. let input = css`
  411. @tailwind base;
  412. @tailwind utilities;
  413. `
  414. return run(input, config).then((result) => {
  415. expect(result.css).toMatchFormattedCss(css`
  416. ${defaults({ defaultRingColor: '#3b82f67f' })}
  417. .ring {
  418. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  419. var(--tw-ring-offset-color);
  420. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  421. var(--tw-ring-color);
  422. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  423. }
  424. `)
  425. })
  426. })
  427. it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
  428. let config = {
  429. future: { respectDefaultRingColorOpacity: true },
  430. content: [{ raw: html`<div class="ring"></div>` }],
  431. corePlugins: { preflight: false },
  432. theme: {
  433. ringOpacity: {
  434. DEFAULT: 0.75,
  435. },
  436. },
  437. }
  438. let input = css`
  439. @tailwind base;
  440. @tailwind utilities;
  441. `
  442. return run(input, config).then((result) => {
  443. expect(result.css).toMatchFormattedCss(css`
  444. ${defaults({ defaultRingColor: '#3b82f67f' })}
  445. .ring {
  446. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  447. var(--tw-ring-offset-color);
  448. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  449. var(--tw-ring-color);
  450. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  451. }
  452. `)
  453. })
  454. })
  455. it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
  456. let config = {
  457. future: { respectDefaultRingColorOpacity: true },
  458. content: [{ raw: html`<div class="ring"></div>` }],
  459. corePlugins: { preflight: false },
  460. theme: {
  461. ringColor: {
  462. DEFAULT: '#ff7f7f',
  463. },
  464. },
  465. }
  466. let input = css`
  467. @tailwind base;
  468. @tailwind utilities;
  469. `
  470. return run(input, config).then((result) => {
  471. expect(result.css).toMatchFormattedCss(css`
  472. ${defaults({ defaultRingColor: '#ff7f7f' })}
  473. .ring {
  474. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  475. var(--tw-ring-offset-color);
  476. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  477. var(--tw-ring-color);
  478. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  479. }
  480. `)
  481. })
  482. })
  483. it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
  484. let config = {
  485. future: { respectDefaultRingColorOpacity: true },
  486. content: [{ raw: html`<div class="ring"></div>` }],
  487. corePlugins: { preflight: false },
  488. theme: {
  489. ringColor: {
  490. DEFAULT: '#ff7f7f00',
  491. },
  492. },
  493. }
  494. let input = css`
  495. @tailwind base;
  496. @tailwind utilities;
  497. `
  498. return run(input, config).then((result) => {
  499. expect(result.css).toMatchFormattedCss(css`
  500. ${defaults({ defaultRingColor: '#ff7f7f00' })}
  501. .ring {
  502. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  503. var(--tw-ring-offset-color);
  504. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  505. var(--tw-ring-color);
  506. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  507. }
  508. `)
  509. })
  510. })
  511. it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
  512. let config = {
  513. content: [{ raw: html`<div class="ring-opacity"></div>` }],
  514. corePlugins: { preflight: false },
  515. theme: {
  516. ringOpacity: {
  517. DEFAULT: '0.33',
  518. },
  519. },
  520. }
  521. let input = css`
  522. @tailwind utilities;
  523. `
  524. return run(input, config).then((result) => {
  525. expect(result.css).toMatchFormattedCss(css``)
  526. })
  527. })
  528. test('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
  529. let config = {
  530. future: { respectDefaultRingColorOpacity: true },
  531. content: [{ raw: html`<div class="ring-opacity"></div>` }],
  532. corePlugins: { preflight: false },
  533. theme: {
  534. ringOpacity: {
  535. DEFAULT: '0.33',
  536. },
  537. },
  538. }
  539. let input = css`
  540. @tailwind utilities;
  541. `
  542. return run(input, config).then((result) => {
  543. expect(result.css).toMatchFormattedCss(css`
  544. .ring-opacity {
  545. --tw-ring-opacity: 0.33;
  546. }
  547. `)
  548. })
  549. })
  550. it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
  551. let config = {
  552. future: { respectDefaultRingColorOpacity: true },
  553. content: [{ raw: html`<div class="ring ring-blue-500"></div>` }],
  554. corePlugins: { preflight: false },
  555. }
  556. let input = css`
  557. @tailwind utilities;
  558. `
  559. return run(input, config).then((result) => {
  560. expect(result.css).toMatchFormattedCss(css`
  561. .ring {
  562. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  563. var(--tw-ring-offset-color);
  564. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  565. var(--tw-ring-color);
  566. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  567. }
  568. .ring-blue-500 {
  569. --tw-ring-opacity: 1;
  570. --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
  571. }
  572. `)
  573. })
  574. })
  575. test('should not crash when group names contain special characters', () => {
  576. let config = {
  577. future: { respectDefaultRingColorOpacity: true },
  578. content: [
  579. {
  580. raw: '<div class="group/${id}"><div class="group-hover/${id}:visible"></div></div>',
  581. },
  582. ],
  583. corePlugins: { preflight: false },
  584. }
  585. let input = css`
  586. @tailwind utilities;
  587. `
  588. return run(input, config).then((result) => {
  589. if (flagEnabled(config, 'oxideParser')) {
  590. expect(result.css).toMatchFormattedCss(css``)
  591. } else {
  592. expect(result.css).toMatchFormattedCss(css`
  593. .group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible {
  594. visibility: visible;
  595. }
  596. `)
  597. }
  598. })
  599. })
  600. it('should not crash when matching variants where utility classes are doubled up', () => {
  601. let config = {
  602. content: [
  603. {
  604. raw: '<div class="hover:foo"></div>',
  605. },
  606. ],
  607. }
  608. let input = css`
  609. @tailwind utilities;
  610. @layer utilities {
  611. .foo.foo {
  612. text-decoration-line: underline;
  613. }
  614. }
  615. `
  616. return run(input, config).then((result) => {
  617. expect(result.css).toMatchFormattedCss(css`
  618. .hover\:foo:hover.hover\:foo:hover {
  619. text-decoration-line: underline;
  620. }
  621. `)
  622. })
  623. })
  624. test('detects quoted arbitrary values containing a slash', async () => {
  625. let config = {
  626. content: [
  627. {
  628. raw: html`<div class="group-[[href^='/']]:hidden"></div>`,
  629. },
  630. ],
  631. }
  632. let input = css`
  633. @tailwind utilities;
  634. `
  635. let result = await run(input, config)
  636. expect(result.css).toMatchFormattedCss(
  637. flagEnabled(config, 'oxideParser')
  638. ? css`
  639. .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
  640. display: none;
  641. }
  642. `
  643. : css`
  644. .hidden,
  645. .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
  646. display: none;
  647. }
  648. `
  649. )
  650. })
  651. test('handled quoted arbitrary values containing escaped spaces', async () => {
  652. let config = {
  653. content: [
  654. {
  655. raw: html`<div class="group-[[href^='_bar']]:hidden"></div>`,
  656. },
  657. ],
  658. }
  659. let input = css`
  660. @tailwind utilities;
  661. `
  662. let result = await run(input, config)
  663. expect(result.css).toMatchFormattedCss(
  664. flagEnabled(config, 'oxideParser')
  665. ? css`
  666. .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
  667. display: none;
  668. }
  669. `
  670. : css`
  671. .hidden,
  672. .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
  673. display: none;
  674. }
  675. `
  676. )
  677. })
  678. test('Skips classes inside :not() when nested inside an at-rule', async () => {
  679. let config = {
  680. content: [
  681. {
  682. raw: html` <div class="disabled !disabled"></div> `,
  683. },
  684. ],
  685. corePlugins: { preflight: false },
  686. plugins: [
  687. function ({ addUtilities }) {
  688. addUtilities({
  689. '.hand:not(.disabled)': {
  690. '@supports (cursor: pointer)': {
  691. cursor: 'pointer',
  692. },
  693. },
  694. })
  695. },
  696. ],
  697. }
  698. let input = css`
  699. @tailwind utilities;
  700. `
  701. // We didn't find the hand class therefore
  702. // nothing should be generated
  703. let result = await run(input, config)
  704. expect(result.css).toMatchFormattedCss(css``)
  705. })
  706. test('Irrelevant rules are removed when applying variants', async () => {
  707. let config = {
  708. content: [
  709. {
  710. raw: html` <div class="md:w-full"></div> `,
  711. },
  712. ],
  713. corePlugins: { preflight: false },
  714. plugins: [
  715. function ({ addUtilities }) {
  716. addUtilities({
  717. '@supports (foo: bar)': {
  718. // This doesn't contain `w-full` so it should not exist in the output
  719. '.outer': { color: 'red' },
  720. '.outer:is(.w-full)': { color: 'green' },
  721. },
  722. })
  723. },
  724. ],
  725. }
  726. let input = css`
  727. @tailwind utilities;
  728. `
  729. // We didn't find the hand class therefore
  730. // nothing should be generated
  731. let result = await run(input, config)
  732. expect(result.css).toMatchFormattedCss(css`
  733. @media (min-width: 768px) {
  734. .md\:w-full {
  735. width: 100%;
  736. }
  737. @supports (foo: bar) {
  738. .outer.md\:w-full {
  739. color: green;
  740. }
  741. }
  742. }
  743. `)
  744. })