arbitrary-variants.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. import { run, html, css, defaults } from './util/run'
  2. test('basic arbitrary variants', () => {
  3. let config = {
  4. content: [{ raw: html`<div class="[&>*]:underline"></div>` }],
  5. corePlugins: { preflight: false },
  6. }
  7. let input = css`
  8. @tailwind base;
  9. @tailwind components;
  10. @tailwind utilities;
  11. `
  12. return run(input, config).then((result) => {
  13. expect(result.css).toMatchFormattedCss(css`
  14. ${defaults}
  15. .\[\&\>\*\]\:underline > * {
  16. text-decoration-line: underline;
  17. }
  18. `)
  19. })
  20. })
  21. test('spaces in selector (using _)', () => {
  22. let config = {
  23. content: [
  24. {
  25. raw: html`<div class="[.a.b_&]:underline"></div>`,
  26. },
  27. ],
  28. corePlugins: { preflight: false },
  29. }
  30. let input = css`
  31. @tailwind base;
  32. @tailwind components;
  33. @tailwind utilities;
  34. `
  35. return run(input, config).then((result) => {
  36. expect(result.css).toMatchFormattedCss(css`
  37. ${defaults}
  38. .a.b .\[\.a\.b_\&\]\:underline {
  39. text-decoration-line: underline;
  40. }
  41. `)
  42. })
  43. })
  44. test('arbitrary variants with modifiers', () => {
  45. let config = {
  46. content: [{ raw: html`<div class="dark:lg:hover:[&>*]:underline"></div>` }],
  47. corePlugins: { preflight: false },
  48. }
  49. let input = css`
  50. @tailwind base;
  51. @tailwind components;
  52. @tailwind utilities;
  53. `
  54. return run(input, config).then((result) => {
  55. expect(result.css).toMatchFormattedCss(css`
  56. ${defaults}
  57. @media (prefers-color-scheme: dark) {
  58. @media (min-width: 1024px) {
  59. .dark\:lg\:hover\:\[\&\>\*\]\:underline > *:hover {
  60. text-decoration-line: underline;
  61. }
  62. }
  63. }
  64. `)
  65. })
  66. })
  67. test('variants without & or an at-rule are ignored', () => {
  68. let config = {
  69. content: [
  70. {
  71. raw: html`
  72. <div class="[div]:underline"></div>
  73. <div class="[:hover]:underline"></div>
  74. <div class="[wtf-bbq]:underline"></div>
  75. <div class="[lol]:hover:underline"></div>
  76. `,
  77. },
  78. ],
  79. corePlugins: { preflight: false },
  80. }
  81. let input = css`
  82. @tailwind base;
  83. @tailwind components;
  84. @tailwind utilities;
  85. `
  86. return run(input, config).then((result) => {
  87. expect(result.css).toMatchFormattedCss(css`
  88. ${defaults}
  89. `)
  90. })
  91. })
  92. test('arbitrary variants are sorted after other variants', () => {
  93. let config = {
  94. content: [{ raw: html`<div class="[&>*]:underline underline lg:underline"></div>` }],
  95. corePlugins: { preflight: false },
  96. }
  97. let input = css`
  98. @tailwind base;
  99. @tailwind components;
  100. @tailwind utilities;
  101. `
  102. return run(input, config).then((result) => {
  103. expect(result.css).toMatchFormattedCss(css`
  104. ${defaults}
  105. .underline {
  106. text-decoration-line: underline;
  107. }
  108. @media (min-width: 1024px) {
  109. .lg\:underline {
  110. text-decoration-line: underline;
  111. }
  112. }
  113. .\[\&\>\*\]\:underline > * {
  114. text-decoration-line: underline;
  115. }
  116. `)
  117. })
  118. })
  119. test('using the important modifier', () => {
  120. let config = {
  121. content: [{ raw: html`<div class="[&>*]:!underline"></div>` }],
  122. corePlugins: { preflight: false },
  123. }
  124. let input = css`
  125. @tailwind base;
  126. @tailwind components;
  127. @tailwind utilities;
  128. `
  129. return run(input, config).then((result) => {
  130. expect(result.css).toMatchFormattedCss(css`
  131. ${defaults}
  132. .\[\&\>\*\]\:\!underline > * {
  133. text-decoration-line: underline !important;
  134. }
  135. `)
  136. })
  137. })
  138. test('at-rules', () => {
  139. let config = {
  140. content: [{ raw: html`<div class="[@supports(what:ever)]:underline"></div>` }],
  141. corePlugins: { preflight: false },
  142. }
  143. let input = css`
  144. @tailwind base;
  145. @tailwind components;
  146. @tailwind utilities;
  147. `
  148. return run(input, config).then((result) => {
  149. expect(result.css).toMatchFormattedCss(css`
  150. ${defaults}
  151. @supports (what: ever) {
  152. .\[\@supports\(what\:ever\)\]\:underline {
  153. text-decoration-line: underline;
  154. }
  155. }
  156. `)
  157. })
  158. })
  159. test('nested at-rules', () => {
  160. let config = {
  161. content: [
  162. {
  163. raw: html`<div class="[@media_screen{@media(hover:hover)}]:underline"></div>`,
  164. },
  165. ],
  166. corePlugins: { preflight: false },
  167. }
  168. let input = css`
  169. @tailwind base;
  170. @tailwind components;
  171. @tailwind utilities;
  172. `
  173. return run(input, config).then((result) => {
  174. expect(result.css).toMatchFormattedCss(css`
  175. ${defaults}
  176. @media screen {
  177. @media (hover: hover) {
  178. .\[\@media_screen\{\@media\(hover\:hover\)\}\]\:underline {
  179. text-decoration-line: underline;
  180. }
  181. }
  182. }
  183. `)
  184. })
  185. })
  186. test('at-rules with selector modifications', () => {
  187. let config = {
  188. content: [{ raw: html`<div class="[@media(hover:hover){&:hover}]:underline"></div>` }],
  189. corePlugins: { preflight: false },
  190. }
  191. let input = css`
  192. @tailwind base;
  193. @tailwind components;
  194. @tailwind utilities;
  195. `
  196. return run(input, config).then((result) => {
  197. expect(result.css).toMatchFormattedCss(css`
  198. ${defaults}
  199. @media (hover: hover) {
  200. .\[\@media\(hover\:hover\)\{\&\:hover\}\]\:underline:hover {
  201. text-decoration-line: underline;
  202. }
  203. }
  204. `)
  205. })
  206. })
  207. test('nested at-rules with selector modifications', () => {
  208. let config = {
  209. content: [
  210. {
  211. raw: html`<div class="[@media_screen{@media(hover:hover){&:hover}}]:underline"></div>`,
  212. },
  213. ],
  214. corePlugins: { preflight: false },
  215. }
  216. let input = css`
  217. @tailwind base;
  218. @tailwind components;
  219. @tailwind utilities;
  220. `
  221. return run(input, config).then((result) => {
  222. expect(result.css).toMatchFormattedCss(css`
  223. ${defaults}
  224. @media screen {
  225. @media (hover: hover) {
  226. .\[\@media_screen\{\@media\(hover\:hover\)\{\&\:hover\}\}\]\:underline:hover {
  227. text-decoration-line: underline;
  228. }
  229. }
  230. }
  231. `)
  232. })
  233. })
  234. test('attribute selectors', () => {
  235. let config = {
  236. content: [{ raw: html`<div class="[&[data-open]]:underline"></div>` }],
  237. corePlugins: { preflight: false },
  238. }
  239. let input = css`
  240. @tailwind base;
  241. @tailwind components;
  242. @tailwind utilities;
  243. `
  244. return run(input, config).then((result) => {
  245. expect(result.css).toMatchFormattedCss(css`
  246. ${defaults}
  247. .\[\&\[data-open\]\]\:underline[data-open] {
  248. text-decoration-line: underline;
  249. }
  250. `)
  251. })
  252. })
  253. test('multiple attribute selectors', () => {
  254. let config = {
  255. content: [{ raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]:underline"></div>` }],
  256. corePlugins: { preflight: false },
  257. }
  258. let input = css`
  259. @tailwind base;
  260. @tailwind components;
  261. @tailwind utilities;
  262. `
  263. return run(input, config).then((result) => {
  264. expect(result.css).toMatchFormattedCss(css`
  265. ${defaults}
  266. .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]\:underline[data-foo][data-bar]:not([data-baz]) {
  267. text-decoration-line: underline;
  268. }
  269. `)
  270. })
  271. })
  272. test('multiple attribute selectors with custom separator (1)', () => {
  273. let config = {
  274. separator: '__',
  275. content: [
  276. { raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]__underline"></div>` },
  277. ],
  278. corePlugins: { preflight: false },
  279. }
  280. let input = css`
  281. @tailwind base;
  282. @tailwind components;
  283. @tailwind utilities;
  284. `
  285. return run(input, config).then((result) => {
  286. expect(result.css).toMatchFormattedCss(css`
  287. ${defaults}
  288. .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]__underline[data-foo][data-bar]:not([data-baz]) {
  289. text-decoration-line: underline;
  290. }
  291. `)
  292. })
  293. })
  294. test('multiple attribute selectors with custom separator (2)', () => {
  295. let config = {
  296. separator: '_@',
  297. content: [
  298. { raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]_@underline"></div>` },
  299. ],
  300. corePlugins: { preflight: false },
  301. }
  302. let input = css`
  303. @tailwind base;
  304. @tailwind components;
  305. @tailwind utilities;
  306. `
  307. return run(input, config).then((result) => {
  308. expect(result.css).toMatchFormattedCss(css`
  309. ${defaults}
  310. .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]_\@underline[data-foo][data-bar]:not([data-baz]) {
  311. text-decoration-line: underline;
  312. }
  313. `)
  314. })
  315. })
  316. test('with @apply', () => {
  317. let config = {
  318. content: [
  319. {
  320. raw: html`<div class="foo"></div>`,
  321. },
  322. ],
  323. corePlugins: { preflight: false },
  324. }
  325. let input = `
  326. @tailwind base;
  327. @tailwind components;
  328. @tailwind utilities;
  329. .foo {
  330. @apply [@media_screen{@media(hover:hover){&:hover}}]:underline;
  331. }
  332. `
  333. return run(input, config).then((result) => {
  334. expect(result.css).toMatchFormattedCss(css`
  335. ${defaults}
  336. @media screen {
  337. @media (hover: hover) {
  338. .foo:hover {
  339. text-decoration-line: underline;
  340. }
  341. }
  342. }
  343. `)
  344. })
  345. })
  346. test('keeps escaped underscores', () => {
  347. let config = {
  348. content: [
  349. {
  350. raw: '<div class="[&_.foo\\_\\_bar]:underline"></div>',
  351. },
  352. ],
  353. corePlugins: { preflight: false },
  354. }
  355. let input = `
  356. @tailwind base;
  357. @tailwind components;
  358. @tailwind utilities;
  359. `
  360. return run(input, config).then((result) => {
  361. expect(result.css).toMatchFormattedCss(css`
  362. ${defaults}
  363. .\[\&_\.foo\\_\\_bar\]\:underline .foo__bar {
  364. text-decoration-line: underline;
  365. }
  366. `)
  367. })
  368. })
  369. test('keeps escaped underscores with multiple arbitrary variants', () => {
  370. let config = {
  371. content: [
  372. {
  373. raw: '<div class="[&_.foo\\_\\_bar]:[&_.bar\\_\\_baz]:underline"></div>',
  374. },
  375. ],
  376. corePlugins: { preflight: false },
  377. }
  378. let input = `
  379. @tailwind base;
  380. @tailwind components;
  381. @tailwind utilities;
  382. `
  383. return run(input, config).then((result) => {
  384. expect(result.css).toMatchFormattedCss(css`
  385. ${defaults}
  386. .\[\&_\.foo\\_\\_bar\]\:\[\&_\.bar\\_\\_baz\]\:underline .bar__baz .foo__bar {
  387. text-decoration-line: underline;
  388. }
  389. `)
  390. })
  391. })
  392. test('keeps escaped underscores in arbitrary variants mixed with normal variants', () => {
  393. let config = {
  394. content: [
  395. {
  396. raw: `
  397. <div class="[&_.foo\\_\\_bar]:hover:underline"></div>
  398. <div class="hover:[&_.foo\\_\\_bar]:underline"></div>
  399. `,
  400. },
  401. ],
  402. corePlugins: { preflight: false },
  403. }
  404. let input = `
  405. @tailwind base;
  406. @tailwind components;
  407. @tailwind utilities;
  408. `
  409. return run(input, config).then((result) => {
  410. expect(result.css).toMatchFormattedCss(css`
  411. ${defaults}
  412. .\[\&_\.foo\\_\\_bar\]\:hover\:underline:hover .foo__bar {
  413. text-decoration-line: underline;
  414. }
  415. .hover\:\[\&_\.foo\\_\\_bar\]\:underline .foo__bar:hover {
  416. text-decoration-line: underline;
  417. }
  418. `)
  419. })
  420. })
  421. test('allows attribute variants with quotes', () => {
  422. let config = {
  423. content: [
  424. {
  425. raw: `
  426. <div class="[&[data-test='2']]:underline"></div>
  427. <div class='[&[data-test="2"]]:underline'></div>
  428. `,
  429. },
  430. ],
  431. corePlugins: { preflight: false },
  432. }
  433. let input = `
  434. @tailwind base;
  435. @tailwind components;
  436. @tailwind utilities;
  437. `
  438. return run(input, config).then((result) => {
  439. expect(result.css).toMatchFormattedCss(css`
  440. ${defaults}
  441. .\[\&\[data-test\=\'2\'\]\]\:underline[data-test="2"] {
  442. text-decoration-line: underline;
  443. }
  444. .\[\&\[data-test\=\"2\"\]\]\:underline[data-test='2'] {
  445. text-decoration-line: underline;
  446. }
  447. `)
  448. })
  449. })
  450. test('classes in arbitrary variants should not be prefixed', () => {
  451. let config = {
  452. prefix: 'tw-',
  453. content: [
  454. {
  455. raw: `
  456. <div class="[.foo_&]:tw-text-red-400">should not be red</div>
  457. <div class="foo">
  458. <div class="[.foo_&]:tw-text-red-400">should be red</div>
  459. </div>
  460. <div class="[&_.foo]:tw-text-red-400">
  461. <div>should not be red</div>
  462. <div class="foo">should be red</div>
  463. </div>
  464. `,
  465. },
  466. ],
  467. corePlugins: { preflight: false },
  468. }
  469. let input = `
  470. @tailwind utilities;
  471. `
  472. return run(input, config).then((result) => {
  473. expect(result.css).toMatchFormattedCss(css`
  474. .foo .\[\.foo_\&\]\:tw-text-red-400 {
  475. --tw-text-opacity: 1;
  476. color: rgb(248 113 113 / var(--tw-text-opacity));
  477. }
  478. .\[\&_\.foo\]\:tw-text-red-400 .foo {
  479. --tw-text-opacity: 1;
  480. color: rgb(248 113 113 / var(--tw-text-opacity));
  481. }
  482. `)
  483. })
  484. })
  485. test('classes in the same arbitrary variant should not be prefixed', () => {
  486. let config = {
  487. prefix: 'tw-',
  488. content: [
  489. {
  490. raw: `
  491. <div class="[.foo_&]:tw-text-red-400 [.foo_&]:tw-bg-white">should not be red</div>
  492. <div class="foo">
  493. <div class="[.foo_&]:tw-text-red-400 [.foo_&]:tw-bg-white">should be red</div>
  494. </div>
  495. <div class="[&_.foo]:tw-text-red-400 [&_.foo]:tw-bg-white">
  496. <div>should not be red</div>
  497. <div class="foo">should be red</div>
  498. </div>
  499. `,
  500. },
  501. ],
  502. corePlugins: { preflight: false },
  503. }
  504. let input = `
  505. @tailwind utilities;
  506. `
  507. return run(input, config).then((result) => {
  508. expect(result.css).toMatchFormattedCss(css`
  509. .foo .\[\.foo_\&\]\:tw-bg-white {
  510. --tw-bg-opacity: 1;
  511. background-color: rgb(255 255 255 / var(--tw-bg-opacity));
  512. }
  513. .foo .\[\.foo_\&\]\:tw-text-red-400 {
  514. --tw-text-opacity: 1;
  515. color: rgb(248 113 113 / var(--tw-text-opacity));
  516. }
  517. .\[\&_\.foo\]\:tw-bg-white .foo {
  518. --tw-bg-opacity: 1;
  519. background-color: rgb(255 255 255 / var(--tw-bg-opacity));
  520. }
  521. .\[\&_\.foo\]\:tw-text-red-400 .foo {
  522. --tw-text-opacity: 1;
  523. color: rgb(248 113 113 / var(--tw-text-opacity));
  524. }
  525. `)
  526. })
  527. })