custom-plugins.test.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917
  1. import createPlugin from '../src/public/create-plugin'
  2. import { run, html, css, defaults } from './util/run'
  3. test('plugins can create utilities with object syntax', () => {
  4. let config = {
  5. content: [
  6. {
  7. raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
  8. },
  9. ],
  10. plugins: [
  11. function ({ addUtilities }) {
  12. addUtilities({
  13. '.custom-object-fill': {
  14. 'object-fit': 'fill',
  15. },
  16. '.custom-object-contain': {
  17. 'object-fit': 'contain',
  18. },
  19. '.custom-object-cover': {
  20. 'object-fit': 'cover',
  21. },
  22. })
  23. },
  24. ],
  25. }
  26. return run('@tailwind utilities', config).then((result) => {
  27. expect(result.css).toMatchFormattedCss(css`
  28. .custom-object-fill {
  29. object-fit: fill;
  30. }
  31. .custom-object-contain {
  32. object-fit: contain;
  33. }
  34. .custom-object-cover {
  35. object-fit: cover;
  36. }
  37. `)
  38. })
  39. })
  40. test('plugins can create utilities with arrays of objects', () => {
  41. let config = {
  42. content: [
  43. {
  44. raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
  45. },
  46. ],
  47. plugins: [
  48. function ({ addUtilities }) {
  49. addUtilities([
  50. {
  51. '.custom-object-fill': {
  52. 'object-fit': 'fill',
  53. },
  54. '.custom-object-contain': {
  55. 'object-fit': 'contain',
  56. },
  57. '.custom-object-cover': {
  58. 'object-fit': 'cover',
  59. },
  60. },
  61. ])
  62. },
  63. ],
  64. }
  65. return run('@tailwind utilities', config).then((result) => {
  66. expect(result.css).toMatchFormattedCss(css`
  67. .custom-object-fill {
  68. object-fit: fill;
  69. }
  70. .custom-object-contain {
  71. object-fit: contain;
  72. }
  73. .custom-object-cover {
  74. object-fit: cover;
  75. }
  76. `)
  77. })
  78. })
  79. test('plugins can create utilities with raw PostCSS nodes', () => {
  80. let config = {
  81. content: [
  82. {
  83. raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
  84. },
  85. ],
  86. plugins: [
  87. function ({ addUtilities, postcss }) {
  88. addUtilities([
  89. postcss.rule({ selector: '.custom-object-fill' }).append([
  90. postcss.decl({
  91. prop: 'object-fit',
  92. value: 'fill',
  93. }),
  94. ]),
  95. postcss.rule({ selector: '.custom-object-contain' }).append([
  96. postcss.decl({
  97. prop: 'object-fit',
  98. value: 'contain',
  99. }),
  100. ]),
  101. postcss.rule({ selector: '.custom-object-cover' }).append([
  102. postcss.decl({
  103. prop: 'object-fit',
  104. value: 'cover',
  105. }),
  106. ]),
  107. ])
  108. },
  109. ],
  110. }
  111. return run('@tailwind utilities', config).then((result) => {
  112. expect(result.css).toMatchFormattedCss(css`
  113. .custom-object-fill {
  114. object-fit: fill;
  115. }
  116. .custom-object-contain {
  117. object-fit: contain;
  118. }
  119. .custom-object-cover {
  120. object-fit: cover;
  121. }
  122. `)
  123. })
  124. })
  125. test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
  126. let config = {
  127. content: [
  128. {
  129. raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
  130. },
  131. ],
  132. plugins: [
  133. function ({ addUtilities, postcss }) {
  134. addUtilities([
  135. {
  136. '.custom-object-fill': {
  137. objectFit: 'fill',
  138. },
  139. },
  140. postcss.rule({ selector: '.custom-object-contain' }).append([
  141. postcss.decl({
  142. prop: 'object-fit',
  143. value: 'contain',
  144. }),
  145. ]),
  146. postcss.rule({ selector: '.custom-object-cover' }).append([
  147. postcss.decl({
  148. prop: 'object-fit',
  149. value: 'cover',
  150. }),
  151. ]),
  152. ])
  153. },
  154. ],
  155. }
  156. return run('@tailwind utilities', config).then((result) => {
  157. expect(result.css).toMatchFormattedCss(css`
  158. .custom-object-fill {
  159. object-fit: fill;
  160. }
  161. .custom-object-contain {
  162. object-fit: contain;
  163. }
  164. .custom-object-cover {
  165. object-fit: cover;
  166. }
  167. `)
  168. })
  169. })
  170. test('plugins can create components with object syntax', () => {
  171. let config = {
  172. content: [
  173. {
  174. raw: html`<button class="btn-blue"></button>`,
  175. },
  176. ],
  177. plugins: [
  178. function ({ addComponents }) {
  179. addComponents({
  180. '.btn-blue': {
  181. backgroundColor: 'blue',
  182. color: 'white',
  183. padding: '.5rem 1rem',
  184. borderRadius: '.25rem',
  185. },
  186. '.btn-blue:hover': {
  187. backgroundColor: 'darkblue',
  188. },
  189. })
  190. },
  191. ],
  192. }
  193. return run('@tailwind components', config).then((result) => {
  194. expect(result.css).toMatchFormattedCss(css`
  195. .btn-blue {
  196. background-color: blue;
  197. color: white;
  198. padding: 0.5rem 1rem;
  199. border-radius: 0.25rem;
  200. }
  201. .btn-blue:hover {
  202. background-color: darkblue;
  203. }
  204. `)
  205. })
  206. })
  207. test('plugins can add base styles with object syntax', () => {
  208. let config = {
  209. content: [
  210. {
  211. raw: html`<img /><button></button>`,
  212. },
  213. ],
  214. plugins: [
  215. function ({ addBase }) {
  216. addBase({
  217. img: {
  218. maxWidth: '100%',
  219. },
  220. button: {
  221. fontFamily: 'inherit',
  222. },
  223. })
  224. },
  225. ],
  226. corePlugins: { preflight: false },
  227. }
  228. return run('@tailwind base', config).then((result) => {
  229. expect(result.css).toMatchFormattedCss(css`
  230. img {
  231. max-width: 100%;
  232. }
  233. button {
  234. font-family: inherit;
  235. }
  236. ${defaults}
  237. `)
  238. })
  239. })
  240. test('plugins can add base styles with raw PostCSS nodes', () => {
  241. let config = {
  242. content: [
  243. {
  244. raw: html`<img /><button></button>`,
  245. },
  246. ],
  247. plugins: [
  248. function ({ addBase, postcss }) {
  249. addBase([
  250. postcss.rule({ selector: 'img' }).append([
  251. postcss.decl({
  252. prop: 'max-width',
  253. value: '100%',
  254. }),
  255. ]),
  256. postcss.rule({ selector: 'button' }).append([
  257. postcss.decl({
  258. prop: 'font-family',
  259. value: 'inherit',
  260. }),
  261. ]),
  262. ])
  263. },
  264. ],
  265. corePlugins: { preflight: false },
  266. }
  267. return run('@tailwind base', config).then((result) => {
  268. expect(result.css).toMatchFormattedCss(css`
  269. img {
  270. max-width: 100%;
  271. }
  272. button {
  273. font-family: inherit;
  274. }
  275. ${defaults}
  276. `)
  277. })
  278. })
  279. test('plugins can create components with raw PostCSS nodes', () => {
  280. let config = {
  281. content: [
  282. {
  283. raw: html`<button class="btn-blue"></button>`,
  284. },
  285. ],
  286. plugins: [
  287. function ({ addComponents, postcss }) {
  288. addComponents([
  289. postcss.rule({ selector: '.btn-blue' }).append([
  290. postcss.decl({
  291. prop: 'background-color',
  292. value: 'blue',
  293. }),
  294. postcss.decl({
  295. prop: 'color',
  296. value: 'white',
  297. }),
  298. postcss.decl({
  299. prop: 'padding',
  300. value: '.5rem 1rem',
  301. }),
  302. postcss.decl({
  303. prop: 'border-radius',
  304. value: '.25rem',
  305. }),
  306. ]),
  307. postcss.rule({ selector: '.btn-blue:hover' }).append([
  308. postcss.decl({
  309. prop: 'background-color',
  310. value: 'darkblue',
  311. }),
  312. ]),
  313. ])
  314. },
  315. ],
  316. }
  317. return run('@tailwind components', config).then((result) => {
  318. expect(result.css).toMatchFormattedCss(css`
  319. .btn-blue {
  320. background-color: blue;
  321. color: white;
  322. padding: 0.5rem 1rem;
  323. border-radius: 0.25rem;
  324. }
  325. .btn-blue:hover {
  326. background-color: darkblue;
  327. }
  328. `)
  329. })
  330. })
  331. test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
  332. let config = {
  333. content: [
  334. {
  335. raw: html`<button class="btn-blue"></button>`,
  336. },
  337. ],
  338. plugins: [
  339. function ({ addComponents, postcss }) {
  340. addComponents([
  341. postcss.rule({ selector: '.btn-blue' }).append([
  342. postcss.decl({
  343. prop: 'background-color',
  344. value: 'blue',
  345. }),
  346. postcss.decl({
  347. prop: 'color',
  348. value: 'white',
  349. }),
  350. postcss.decl({
  351. prop: 'padding',
  352. value: '.5rem 1rem',
  353. }),
  354. postcss.decl({
  355. prop: 'border-radius',
  356. value: '.25rem',
  357. }),
  358. ]),
  359. {
  360. '.btn-blue:hover': {
  361. backgroundColor: 'darkblue',
  362. },
  363. },
  364. ])
  365. },
  366. ],
  367. }
  368. return run('@tailwind components', config).then((result) => {
  369. expect(result.css).toMatchFormattedCss(css`
  370. .btn-blue {
  371. background-color: blue;
  372. color: white;
  373. padding: 0.5rem 1rem;
  374. border-radius: 0.25rem;
  375. }
  376. .btn-blue:hover {
  377. background-color: darkblue;
  378. }
  379. `)
  380. })
  381. })
  382. test('plugins can create components with media queries with object syntax', () => {
  383. let config = {
  384. content: [
  385. {
  386. raw: html`<div class="custom-container"></div>`,
  387. },
  388. ],
  389. plugins: [
  390. function ({ addComponents }) {
  391. addComponents({
  392. '.custom-container': {
  393. width: '100%',
  394. },
  395. '@media (min-width: 100px)': {
  396. '.custom-container': {
  397. maxWidth: '100px',
  398. },
  399. },
  400. '@media (min-width: 200px)': {
  401. '.custom-container': {
  402. maxWidth: '200px',
  403. },
  404. },
  405. '@media (min-width: 300px)': {
  406. '.custom-container': {
  407. maxWidth: '300px',
  408. },
  409. },
  410. })
  411. },
  412. ],
  413. }
  414. return run('@tailwind components', config).then((result) => {
  415. expect(result.css).toMatchFormattedCss(css`
  416. .custom-container {
  417. width: 100%;
  418. }
  419. @media (min-width: 100px) {
  420. .custom-container {
  421. max-width: 100px;
  422. }
  423. }
  424. @media (min-width: 200px) {
  425. .custom-container {
  426. max-width: 200px;
  427. }
  428. }
  429. @media (min-width: 300px) {
  430. .custom-container {
  431. max-width: 300px;
  432. }
  433. }
  434. `)
  435. })
  436. })
  437. test('media queries can be defined multiple times using objects-in-array syntax', () => {
  438. let config = {
  439. content: [
  440. {
  441. raw: html`<div class="custom-container"></div>
  442. <button class="btn"></button>`,
  443. },
  444. ],
  445. plugins: [
  446. function ({ addComponents }) {
  447. addComponents([
  448. {
  449. '.custom-container': {
  450. width: '100%',
  451. },
  452. '@media (min-width: 100px)': {
  453. '.custom-container': {
  454. maxWidth: '100px',
  455. },
  456. },
  457. },
  458. {
  459. '.btn': {
  460. padding: '1rem .5rem',
  461. display: 'block',
  462. },
  463. '@media (min-width: 100px)': {
  464. '.btn': {
  465. display: 'inline-block',
  466. },
  467. },
  468. },
  469. ])
  470. },
  471. ],
  472. }
  473. return run('@tailwind components', config).then((result) => {
  474. expect(result.css).toMatchFormattedCss(css`
  475. .custom-container {
  476. width: 100%;
  477. }
  478. @media (min-width: 100px) {
  479. .custom-container {
  480. max-width: 100px;
  481. }
  482. }
  483. .btn {
  484. padding: 1rem 0.5rem;
  485. display: block;
  486. }
  487. @media (min-width: 100px) {
  488. .btn {
  489. display: inline-block;
  490. }
  491. }
  492. `)
  493. })
  494. })
  495. test('plugins can create nested rules', () => {
  496. let config = {
  497. content: [{ raw: html`<button class="btn-blue"></button>` }],
  498. plugins: [
  499. function ({ addComponents }) {
  500. addComponents({
  501. '.btn-blue': {
  502. backgroundColor: 'blue',
  503. color: 'white',
  504. padding: '.5rem 1rem',
  505. borderRadius: '.25rem',
  506. '&:hover': {
  507. backgroundColor: 'darkblue',
  508. },
  509. '@media (min-width: 500px)': {
  510. '&:hover': {
  511. backgroundColor: 'orange',
  512. },
  513. },
  514. '> a': {
  515. color: 'red',
  516. },
  517. 'h1 &': {
  518. color: 'purple',
  519. },
  520. },
  521. })
  522. },
  523. ],
  524. }
  525. return run('@tailwind components', config).then((result) => {
  526. expect(result.css).toMatchFormattedCss(css`
  527. .btn-blue {
  528. background-color: blue;
  529. color: white;
  530. padding: 0.5rem 1rem;
  531. border-radius: 0.25rem;
  532. }
  533. .btn-blue:hover {
  534. background-color: darkblue;
  535. }
  536. @media (min-width: 500px) {
  537. .btn-blue:hover {
  538. background-color: orange;
  539. }
  540. }
  541. .btn-blue > a {
  542. color: red;
  543. }
  544. h1 .btn-blue {
  545. color: purple;
  546. }
  547. `)
  548. })
  549. })
  550. test('plugins can create rules with escaped selectors', () => {
  551. let config = {
  552. content: [{ raw: html`<div class="custom-top-1/4"></div>` }],
  553. plugins: [
  554. function ({ e, addUtilities }) {
  555. addUtilities({
  556. [`.${e('custom-top-1/4')}`]: {
  557. top: '25%',
  558. },
  559. })
  560. },
  561. ],
  562. }
  563. return run('@tailwind utilities', config).then((result) => {
  564. expect(result.css).toMatchFormattedCss(css`
  565. .custom-top-1\/4 {
  566. top: 25%;
  567. }
  568. `)
  569. })
  570. })
  571. test('plugins can access the current config', () => {
  572. let config = {
  573. content: [{ raw: html`<div class="custom-container"></div>` }],
  574. plugins: [
  575. function ({ addComponents, config }) {
  576. let containerClasses = [
  577. {
  578. '.custom-container': {
  579. width: '100%',
  580. },
  581. },
  582. ]
  583. for (let maxWidth of Object.values(config('theme.customScreens'))) {
  584. containerClasses.push({
  585. [`@media (min-width: ${maxWidth})`]: {
  586. '.custom-container': { maxWidth },
  587. },
  588. })
  589. }
  590. addComponents(containerClasses)
  591. },
  592. ],
  593. theme: {
  594. customScreens: {
  595. sm: '576px',
  596. md: '768px',
  597. lg: '992px',
  598. xl: '1200px',
  599. },
  600. },
  601. }
  602. return run('@tailwind components', config).then((result) => {
  603. expect(result.css).toMatchFormattedCss(css`
  604. .custom-container {
  605. width: 100%;
  606. }
  607. @media (min-width: 576px) {
  608. .custom-container {
  609. max-width: 576px;
  610. }
  611. }
  612. @media (min-width: 768px) {
  613. .custom-container {
  614. max-width: 768px;
  615. }
  616. }
  617. @media (min-width: 992px) {
  618. .custom-container {
  619. max-width: 992px;
  620. }
  621. }
  622. @media (min-width: 1200px) {
  623. .custom-container {
  624. max-width: 1200px;
  625. }
  626. }
  627. `)
  628. })
  629. })
  630. test('plugins can check if corePlugins are enabled', () => {
  631. let config = {
  632. content: [{ raw: html`<div class="test"></div>` }],
  633. plugins: [
  634. function ({ addUtilities, corePlugins }) {
  635. addUtilities({
  636. '.test': {
  637. 'text-color': corePlugins('textColor') ? 'true' : 'false',
  638. opacity: corePlugins('opacity') ? 'true' : 'false',
  639. },
  640. })
  641. },
  642. ],
  643. corePlugins: { textColor: false },
  644. }
  645. return run('@tailwind utilities', config).then((result) => {
  646. expect(result.css).toMatchFormattedCss(css`
  647. .test {
  648. text-color: false;
  649. opacity: true;
  650. }
  651. `)
  652. })
  653. })
  654. test('plugins can check if corePlugins are enabled when using array white-listing', () => {
  655. let config = {
  656. content: [{ raw: html`<div class="test"></div>` }],
  657. plugins: [
  658. function ({ addUtilities, corePlugins }) {
  659. addUtilities({
  660. '.test': {
  661. 'text-color': corePlugins('textColor') ? 'true' : 'false',
  662. opacity: corePlugins('opacity') ? 'true' : 'false',
  663. },
  664. })
  665. },
  666. ],
  667. corePlugins: ['textColor'],
  668. }
  669. return run('@tailwind utilities', config).then((result) => {
  670. expect(result.css).toMatchFormattedCss(css`
  671. .test {
  672. text-color: true;
  673. opacity: false;
  674. }
  675. `)
  676. })
  677. })
  678. test('plugins can provide fallbacks to keys missing from the config', () => {
  679. let config = {
  680. content: [{ raw: html`<button class="btn"></button>` }],
  681. plugins: [
  682. function ({ addComponents, config }) {
  683. addComponents({
  684. '.btn': {
  685. borderRadius: config('borderRadius.default', '.25rem'),
  686. },
  687. })
  688. },
  689. ],
  690. theme: {
  691. borderRadius: {
  692. 1: '1px',
  693. 2: '2px',
  694. 4: '4px',
  695. 8: '8px',
  696. },
  697. },
  698. }
  699. return run('@tailwind components', config).then((result) => {
  700. expect(result.css).toMatchFormattedCss(css`
  701. .btn {
  702. border-radius: 0.25rem;
  703. }
  704. `)
  705. })
  706. })
  707. test('plugins can add multiple sets of utilities and components', () => {
  708. let config = {
  709. content: [
  710. {
  711. raw: html`<button class="btn"></button>
  712. <div class="card custom-skew-12deg custom-border-collapse"></div>`,
  713. },
  714. ],
  715. plugins: [
  716. function ({ addUtilities, addComponents }) {
  717. addComponents({
  718. '.card': {
  719. padding: '1rem',
  720. borderRadius: '.25rem',
  721. },
  722. })
  723. addUtilities({
  724. '.custom-skew-12deg': {
  725. transform: 'skewY(-12deg)',
  726. },
  727. })
  728. addComponents({
  729. '.btn': {
  730. padding: '1rem .5rem',
  731. display: 'inline-block',
  732. },
  733. })
  734. addUtilities({
  735. '.custom-border-collapse': {
  736. borderCollapse: 'collapse',
  737. },
  738. })
  739. },
  740. ],
  741. }
  742. return run('@tailwind components; @tailwind utilities;', config).then((result) => {
  743. expect(result.css).toMatchFormattedCss(css`
  744. .card {
  745. padding: 1rem;
  746. border-radius: 0.25rem;
  747. }
  748. .btn {
  749. padding: 1rem 0.5rem;
  750. display: inline-block;
  751. }
  752. .custom-skew-12deg {
  753. transform: skewY(-12deg);
  754. }
  755. .custom-border-collapse {
  756. border-collapse: collapse;
  757. }
  758. `)
  759. })
  760. })
  761. test('plugins respect prefix and important options by default when adding utilities', () => {
  762. let config = {
  763. prefix: 'tw-',
  764. important: true,
  765. content: [{ raw: html`<div class="tw-custom-rotate-90"></div>` }],
  766. plugins: [
  767. function ({ addUtilities }) {
  768. addUtilities({
  769. '.custom-rotate-90': {
  770. transform: 'rotate(90deg)',
  771. },
  772. })
  773. },
  774. ],
  775. }
  776. return run('@tailwind utilities', config).then((result) => {
  777. expect(result.css).toMatchFormattedCss(css`
  778. .tw-custom-rotate-90 {
  779. transform: rotate(90deg) !important;
  780. }
  781. `)
  782. })
  783. })
  784. test('when important is a selector it is used to scope utilities instead of adding !important', () => {
  785. let config = {
  786. prefix: 'tw-',
  787. important: '#app',
  788. content: [{ raw: html`<div class="tw-custom-rotate-90"></div>` }],
  789. plugins: [
  790. function ({ addUtilities }) {
  791. addUtilities({
  792. '.custom-rotate-90': {
  793. transform: 'rotate(90deg)',
  794. },
  795. })
  796. },
  797. ],
  798. }
  799. return run('@tailwind utilities', config).then((result) => {
  800. expect(result.css).toMatchFormattedCss(css`
  801. #app .tw-custom-rotate-90 {
  802. transform: rotate(90deg);
  803. }
  804. `)
  805. })
  806. })
  807. test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
  808. let config = {
  809. important: '#app',
  810. content: [{ raw: html`<div class="custom-rotate-90 custom-rotate-1/4"></div>` }],
  811. plugins: [
  812. function ({ addUtilities }) {
  813. addUtilities({
  814. '.custom-rotate-90, .custom-rotate-1\\/4': {
  815. transform: 'rotate(90deg)',
  816. },
  817. })
  818. },
  819. ],
  820. }
  821. return run('@tailwind utilities', config).then((result) => {
  822. expect(result.css).toMatchFormattedCss(css`
  823. #app .custom-rotate-90,
  824. #app .custom-rotate-1\/4 {
  825. transform: rotate(90deg);
  826. }
  827. `)
  828. })
  829. })
  830. test('important utilities are not made double important when important option is used', () => {
  831. let config = {
  832. important: true,
  833. content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
  834. plugins: [
  835. function ({ addUtilities }) {
  836. addUtilities({
  837. '.custom-rotate-90': {
  838. transform: 'rotate(90deg) !important',
  839. },
  840. })
  841. },
  842. ],
  843. }
  844. return run('@tailwind utilities', config).then((result) => {
  845. expect(result.css).toMatchFormattedCss(css`
  846. .custom-rotate-90 {
  847. transform: rotate(90deg) !important;
  848. }
  849. `)
  850. })
  851. })
  852. test("component declarations respect the 'prefix' option by default", () => {
  853. let config = {
  854. prefix: 'tw-',
  855. content: [{ raw: html`<button class="tw-btn-blue"></button>` }],
  856. plugins: [
  857. function ({ addComponents }) {
  858. addComponents({
  859. '.btn-blue': {
  860. backgroundColor: 'blue',
  861. },
  862. })
  863. },
  864. ],
  865. }
  866. return run('@tailwind components', config).then((result) => {
  867. expect(result.css).toMatchFormattedCss(css`
  868. .tw-btn-blue {
  869. background-color: blue;
  870. }
  871. `)
  872. })
  873. })
  874. test('all selectors in a rule are prefixed', () => {
  875. let config = {
  876. prefix: 'tw-',
  877. content: [
  878. {
  879. raw: html`<button class="tw-btn-blue tw-btn-red"></button>
  880. <div class="tw-custom-rotate-90 tw-custom-rotate-1/4"></div>`,
  881. },
  882. ],
  883. plugins: [
  884. function ({ addUtilities, addComponents }) {
  885. addUtilities({
  886. '.custom-rotate-90, .custom-rotate-1\\/4': {
  887. transform: 'rotate(90deg)',
  888. },
  889. })
  890. addComponents({
  891. '.btn-blue, .btn-red': {
  892. padding: '10px',
  893. },
  894. })
  895. },
  896. ],
  897. }
  898. return run('@tailwind components', config).then((result) => {
  899. expect(result.css).toMatchFormattedCss(css`
  900. .tw-btn-blue,
  901. .tw-btn-red {
  902. padding: 10px;
  903. }
  904. `)
  905. })
  906. })
  907. test("component declarations can optionally ignore 'prefix' option", () => {
  908. let config = {
  909. prefix: 'tw-',
  910. content: [{ raw: html`<button class="btn-blue"></button>` }],
  911. plugins: [
  912. function ({ addComponents }) {
  913. addComponents(
  914. {
  915. '.btn-blue': {
  916. backgroundColor: 'blue',
  917. },
  918. },
  919. { respectPrefix: false }
  920. )
  921. },
  922. ],
  923. }
  924. return run('@tailwind components', config).then((result) => {
  925. expect(result.css).toMatchFormattedCss(css`
  926. .btn-blue {
  927. background-color: blue;
  928. }
  929. `)
  930. })
  931. })
  932. test("component declarations are not affected by the 'important' option", () => {
  933. let config = {
  934. important: true,
  935. content: [{ raw: html`<button class="btn-blue"></button>` }],
  936. plugins: [
  937. function ({ addComponents }) {
  938. addComponents({
  939. '.btn-blue': {
  940. backgroundColor: 'blue',
  941. },
  942. })
  943. },
  944. ],
  945. }
  946. return run('@tailwind components', config).then((result) => {
  947. expect(result.css).toMatchFormattedCss(css`
  948. .btn-blue {
  949. background-color: blue;
  950. }
  951. `)
  952. })
  953. })
  954. test("plugins can apply the user's chosen prefix to components manually", () => {
  955. let config = {
  956. prefix: 'tw-',
  957. content: [{ raw: html`<button class="tw-btn-blue"></button>` }],
  958. plugins: [
  959. function ({ addComponents, prefix }) {
  960. addComponents(
  961. {
  962. [prefix('.btn-blue')]: {
  963. backgroundColor: 'blue',
  964. },
  965. },
  966. { respectPrefix: false }
  967. )
  968. },
  969. ],
  970. }
  971. return run('@tailwind components', config).then((result) => {
  972. expect(result.css).toMatchFormattedCss(css`
  973. .tw-btn-blue {
  974. background-color: blue;
  975. }
  976. `)
  977. })
  978. })
  979. test('prefix can optionally be ignored for utilities', () => {
  980. let config = {
  981. prefix: 'tw-',
  982. content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
  983. plugins: [
  984. function ({ addUtilities }) {
  985. addUtilities(
  986. {
  987. '.custom-rotate-90': {
  988. transform: 'rotate(90deg)',
  989. },
  990. },
  991. { respectPrefix: false }
  992. )
  993. },
  994. ],
  995. }
  996. return run('@tailwind utilities', config).then((result) => {
  997. expect(result.css).toMatchFormattedCss(css`
  998. .custom-rotate-90 {
  999. transform: rotate(90deg);
  1000. }
  1001. `)
  1002. })
  1003. })
  1004. test('important can optionally be ignored for utilities', () => {
  1005. let config = {
  1006. important: true,
  1007. content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
  1008. plugins: [
  1009. function ({ addUtilities }) {
  1010. addUtilities(
  1011. {
  1012. '.custom-rotate-90': {
  1013. transform: 'rotate(90deg)',
  1014. },
  1015. },
  1016. { respectImportant: false }
  1017. )
  1018. },
  1019. ],
  1020. }
  1021. return run('@tailwind utilities', config).then((result) => {
  1022. expect(result.css).toMatchFormattedCss(css`
  1023. .custom-rotate-90 {
  1024. transform: rotate(90deg);
  1025. }
  1026. `)
  1027. })
  1028. })
  1029. test('prefix will prefix all classes in a selector', () => {
  1030. let config = {
  1031. prefix: 'tw-',
  1032. content: [{ raw: html`<div class="tw-btn-blue tw-w-1/4"></div>` }],
  1033. plugins: [
  1034. function ({ addComponents, prefix }) {
  1035. addComponents(
  1036. {
  1037. [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
  1038. backgroundColor: 'blue',
  1039. },
  1040. },
  1041. { respectPrefix: false }
  1042. )
  1043. },
  1044. ],
  1045. }
  1046. return run('@tailwind components', config).then((result) => {
  1047. expect(result.css).toMatchFormattedCss(css`
  1048. .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
  1049. background-color: blue;
  1050. }
  1051. `)
  1052. })
  1053. })
  1054. test('plugins can be provided as an object with a handler function', () => {
  1055. let config = {
  1056. content: [
  1057. {
  1058. raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
  1059. },
  1060. ],
  1061. plugins: [
  1062. {
  1063. handler({ addUtilities }) {
  1064. addUtilities({
  1065. '.custom-object-fill': {
  1066. 'object-fit': 'fill',
  1067. },
  1068. '.custom-object-contain': {
  1069. 'object-fit': 'contain',
  1070. },
  1071. '.custom-object-cover': {
  1072. 'object-fit': 'cover',
  1073. },
  1074. })
  1075. },
  1076. },
  1077. ],
  1078. }
  1079. return run('@tailwind utilities', config).then((result) => {
  1080. expect(result.css).toMatchFormattedCss(css`
  1081. .custom-object-fill {
  1082. object-fit: fill;
  1083. }
  1084. .custom-object-contain {
  1085. object-fit: contain;
  1086. }
  1087. .custom-object-cover {
  1088. object-fit: cover;
  1089. }
  1090. `)
  1091. })
  1092. })
  1093. test('plugins can provide a config but no handler', () => {
  1094. let config = {
  1095. content: [
  1096. {
  1097. raw: html`<div
  1098. class="tw-custom-object-fill tw-custom-object-contain tw-custom-object-cover"
  1099. ></div>`,
  1100. },
  1101. ],
  1102. plugins: [
  1103. {
  1104. config: {
  1105. prefix: 'tw-',
  1106. },
  1107. },
  1108. {
  1109. handler({ addUtilities }) {
  1110. addUtilities({
  1111. '.custom-object-fill': {
  1112. 'object-fit': 'fill',
  1113. },
  1114. '.custom-object-contain': {
  1115. 'object-fit': 'contain',
  1116. },
  1117. '.custom-object-cover': {
  1118. 'object-fit': 'cover',
  1119. },
  1120. })
  1121. },
  1122. },
  1123. ],
  1124. }
  1125. return run('@tailwind utilities', config).then((result) => {
  1126. expect(result.css).toMatchFormattedCss(css`
  1127. .tw-custom-object-fill {
  1128. object-fit: fill;
  1129. }
  1130. .tw-custom-object-contain {
  1131. object-fit: contain;
  1132. }
  1133. .tw-custom-object-cover {
  1134. object-fit: cover;
  1135. }
  1136. `)
  1137. })
  1138. })
  1139. test('plugins can be created using the `createPlugin` function', () => {
  1140. let config = {
  1141. content: [{ raw: html`<div class="test-sm test-md test-lg hover:test-sm sm:test-sm"></div>` }],
  1142. corePlugins: [],
  1143. theme: {
  1144. screens: {
  1145. sm: '400px',
  1146. },
  1147. },
  1148. plugins: [
  1149. createPlugin(
  1150. function ({ addUtilities, theme }) {
  1151. addUtilities(
  1152. Object.fromEntries(
  1153. Object.entries(theme('testPlugin')).map(([k, v]) => [
  1154. `.test-${k}`,
  1155. { testProperty: v },
  1156. ])
  1157. )
  1158. )
  1159. },
  1160. {
  1161. theme: {
  1162. testPlugin: {
  1163. sm: '1rem',
  1164. md: '2rem',
  1165. lg: '3rem',
  1166. },
  1167. },
  1168. }
  1169. ),
  1170. ],
  1171. }
  1172. return run('@tailwind utilities', config).then((result) => {
  1173. expect(result.css).toMatchFormattedCss(css`
  1174. .test-sm {
  1175. test-property: 1rem;
  1176. }
  1177. .test-md {
  1178. test-property: 2rem;
  1179. }
  1180. .test-lg {
  1181. test-property: 3rem;
  1182. }
  1183. .hover\:test-sm:hover {
  1184. test-property: 1rem;
  1185. }
  1186. @media (min-width: 400px) {
  1187. .sm\:test-sm {
  1188. test-property: 1rem;
  1189. }
  1190. }
  1191. `)
  1192. })
  1193. })
  1194. test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
  1195. let plugin = createPlugin.withOptions(
  1196. function ({ className }) {
  1197. return function ({ addUtilities, theme }) {
  1198. addUtilities(
  1199. Object.fromEntries(
  1200. Object.entries(theme('testPlugin')).map(([k, v]) => [
  1201. `.${className}-${k}`,
  1202. { testProperty: v },
  1203. ])
  1204. )
  1205. )
  1206. }
  1207. },
  1208. function () {
  1209. return {
  1210. theme: {
  1211. testPlugin: {
  1212. sm: '1rem',
  1213. md: '2rem',
  1214. lg: '3rem',
  1215. },
  1216. },
  1217. }
  1218. }
  1219. )
  1220. let config = {
  1221. content: [
  1222. { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
  1223. ],
  1224. corePlugins: [],
  1225. theme: {
  1226. screens: {
  1227. sm: '400px',
  1228. },
  1229. },
  1230. plugins: [plugin({ className: 'banana' })],
  1231. }
  1232. return run('@tailwind utilities', config).then((result) => {
  1233. expect(result.css).toMatchFormattedCss(css`
  1234. .banana-sm {
  1235. test-property: 1rem;
  1236. }
  1237. .banana-md {
  1238. test-property: 2rem;
  1239. }
  1240. .banana-lg {
  1241. test-property: 3rem;
  1242. }
  1243. .hover\:banana-sm:hover {
  1244. test-property: 1rem;
  1245. }
  1246. @media (min-width: 400px) {
  1247. .sm\:banana-sm {
  1248. test-property: 1rem;
  1249. }
  1250. }
  1251. `)
  1252. })
  1253. })
  1254. test('plugins should cache correctly', () => {
  1255. let plugin = createPlugin.withOptions(({ className = 'banana' } = {}) => ({ addComponents }) => {
  1256. addComponents({ [`.${className}`]: { position: 'absolute' } })
  1257. })
  1258. let config = {
  1259. content: [{ raw: html`<div class="banana sm:banana apple sm:apple"></div>` }],
  1260. corePlugins: [],
  1261. theme: {
  1262. screens: {
  1263. sm: '400px',
  1264. },
  1265. },
  1266. }
  1267. function internalRun(options = {}) {
  1268. return run('@tailwind components', {
  1269. ...config,
  1270. plugins: [plugin(options)],
  1271. })
  1272. }
  1273. return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
  1274. ([result1, result2]) => {
  1275. let expected1 = css`
  1276. .banana {
  1277. position: absolute;
  1278. }
  1279. @media (min-width: 400px) {
  1280. .sm\:banana {
  1281. position: absolute;
  1282. }
  1283. }
  1284. `
  1285. let expected2 = css`
  1286. .apple {
  1287. position: absolute;
  1288. }
  1289. @media (min-width: 400px) {
  1290. .sm\:apple {
  1291. position: absolute;
  1292. }
  1293. }
  1294. `
  1295. expect(result1.css).toMatchCss(expected1)
  1296. expect(result2.css).toMatchCss(expected2)
  1297. }
  1298. )
  1299. })
  1300. test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
  1301. let plugin = createPlugin.withOptions(
  1302. function ({ className } = { className: 'banana' }) {
  1303. return function ({ addUtilities, theme }) {
  1304. addUtilities(
  1305. Object.fromEntries(
  1306. Object.entries(theme('testPlugin')).map(([k, v]) => [
  1307. `.${className}-${k}`,
  1308. { testProperty: v },
  1309. ])
  1310. )
  1311. )
  1312. }
  1313. },
  1314. function () {
  1315. return {
  1316. theme: {
  1317. testPlugin: {
  1318. sm: '1rem',
  1319. md: '2rem',
  1320. lg: '3rem',
  1321. },
  1322. },
  1323. }
  1324. }
  1325. )
  1326. let config = {
  1327. content: [
  1328. { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
  1329. ],
  1330. corePlugins: [],
  1331. theme: {
  1332. screens: {
  1333. sm: '400px',
  1334. },
  1335. },
  1336. plugins: [plugin],
  1337. }
  1338. return run('@tailwind utilities', config).then((result) => {
  1339. expect(result.css).toMatchFormattedCss(css`
  1340. .banana-sm {
  1341. test-property: 1rem;
  1342. }
  1343. .banana-md {
  1344. test-property: 2rem;
  1345. }
  1346. .banana-lg {
  1347. test-property: 3rem;
  1348. }
  1349. .hover\:banana-sm:hover {
  1350. test-property: 1rem;
  1351. }
  1352. @media (min-width: 400px) {
  1353. .sm\:banana-sm {
  1354. test-property: 1rem;
  1355. }
  1356. }
  1357. `)
  1358. })
  1359. })
  1360. test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
  1361. let plugin = createPlugin.withOptions(function ({ className }) {
  1362. return function ({ addUtilities, theme }) {
  1363. addUtilities(
  1364. Object.fromEntries(
  1365. Object.entries(theme('testPlugin')).map(([k, v]) => [
  1366. `.${className}-${k}`,
  1367. { testProperty: v },
  1368. ])
  1369. )
  1370. )
  1371. }
  1372. })
  1373. let config = {
  1374. content: [
  1375. { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
  1376. ],
  1377. corePlugins: [],
  1378. theme: {
  1379. screens: {
  1380. sm: '400px',
  1381. },
  1382. testPlugin: {
  1383. sm: '1px',
  1384. md: '2px',
  1385. lg: '3px',
  1386. },
  1387. },
  1388. plugins: [plugin({ className: 'banana' })],
  1389. }
  1390. return run('@tailwind utilities', config).then((result) => {
  1391. expect(result.css).toMatchFormattedCss(css`
  1392. .banana-sm {
  1393. test-property: 1px;
  1394. }
  1395. .banana-md {
  1396. test-property: 2px;
  1397. }
  1398. .banana-lg {
  1399. test-property: 3px;
  1400. }
  1401. .hover\:banana-sm:hover {
  1402. test-property: 1px;
  1403. }
  1404. @media (min-width: 400px) {
  1405. .sm\:banana-sm {
  1406. test-property: 1px;
  1407. }
  1408. }
  1409. `)
  1410. })
  1411. })
  1412. test('keyframes are not escaped', () => {
  1413. let config = {
  1414. content: [{ raw: html`<div class="foo-[abc] md:foo-[def]"></div>` }],
  1415. corePlugins: { preflight: false },
  1416. plugins: [
  1417. function ({ matchUtilities }) {
  1418. matchUtilities({
  1419. foo: (value) => {
  1420. return {
  1421. [`@keyframes ${value}`]: {
  1422. '25.001%': {
  1423. color: 'black',
  1424. },
  1425. },
  1426. animation: `${value} 1s infinite`,
  1427. }
  1428. },
  1429. })
  1430. },
  1431. ],
  1432. }
  1433. return run('@tailwind utilities', config).then((result) => {
  1434. expect(result.css).toMatchFormattedCss(css`
  1435. @keyframes abc {
  1436. 25.001% {
  1437. color: black;
  1438. }
  1439. }
  1440. .foo-\[abc\] {
  1441. animation: abc 1s infinite;
  1442. }
  1443. @media (min-width: 768px) {
  1444. @keyframes def {
  1445. 25.001% {
  1446. color: black;
  1447. }
  1448. }
  1449. .md\:foo-\[def\] {
  1450. animation: def 1s infinite;
  1451. }
  1452. }
  1453. `)
  1454. })
  1455. })
  1456. test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
  1457. let config = {
  1458. content: [{ raw: html`<div class="foo"></div>` }],
  1459. corePlugins: [],
  1460. theme: {
  1461. fontSize: {
  1462. sm: ['14px', '20px'],
  1463. },
  1464. },
  1465. plugins: [
  1466. function ({ addComponents, theme }) {
  1467. addComponents({
  1468. '.foo': {
  1469. fontSize: theme('fontSize.sm'),
  1470. },
  1471. })
  1472. },
  1473. ],
  1474. }
  1475. return run('@tailwind components', config).then((result) => {
  1476. expect(result.css).toMatchFormattedCss(css`
  1477. .foo {
  1478. font-size: 14px;
  1479. }
  1480. `)
  1481. })
  1482. })
  1483. test('outlines are retrieved without outline-offset using theme function', () => {
  1484. let config = {
  1485. content: [{ raw: html`<div class="foo"></div>` }],
  1486. corePlugins: [],
  1487. theme: {
  1488. outline: {
  1489. black: ['2px dotted black', '4px'],
  1490. },
  1491. },
  1492. plugins: [
  1493. function ({ addComponents, theme }) {
  1494. addComponents({
  1495. '.foo': {
  1496. outline: theme('outline.black'),
  1497. },
  1498. })
  1499. },
  1500. ],
  1501. }
  1502. return run('@tailwind components', config).then((result) => {
  1503. expect(result.css).toMatchFormattedCss(css`
  1504. .foo {
  1505. outline: 2px dotted black;
  1506. }
  1507. `)
  1508. })
  1509. })
  1510. test('box-shadow values are joined when retrieved using the theme function', () => {
  1511. let config = {
  1512. content: [{ raw: html`<div class="foo"></div>` }],
  1513. corePlugins: [],
  1514. theme: {
  1515. boxShadow: {
  1516. lol: ['width', 'height'],
  1517. },
  1518. },
  1519. plugins: [
  1520. function ({ addComponents, theme }) {
  1521. addComponents({
  1522. '.foo': {
  1523. boxShadow: theme('boxShadow.lol'),
  1524. },
  1525. })
  1526. },
  1527. ],
  1528. }
  1529. return run('@tailwind components', config).then((result) => {
  1530. expect(result.css).toMatchFormattedCss(css`
  1531. .foo {
  1532. box-shadow: width, height;
  1533. }
  1534. `)
  1535. })
  1536. })
  1537. test('transition-property values are joined when retrieved using the theme function', () => {
  1538. let config = {
  1539. content: [{ raw: html`<div class="foo"></div>` }],
  1540. corePlugins: [],
  1541. theme: {
  1542. transitionProperty: {
  1543. lol: ['width', 'height'],
  1544. },
  1545. },
  1546. plugins: [
  1547. function ({ addComponents, theme }) {
  1548. addComponents({
  1549. '.foo': {
  1550. transitionProperty: theme('transitionProperty.lol'),
  1551. },
  1552. })
  1553. },
  1554. ],
  1555. }
  1556. return run('@tailwind components', config).then((result) => {
  1557. expect(result.css).toMatchFormattedCss(css`
  1558. .foo {
  1559. transition-property: width, height;
  1560. }
  1561. `)
  1562. })
  1563. })
  1564. test('transition-duration values are joined when retrieved using the theme function', () => {
  1565. let config = {
  1566. content: [{ raw: html`<div class="foo"></div>` }],
  1567. corePlugins: [],
  1568. theme: {
  1569. transitionDuration: {
  1570. lol: ['width', 'height'],
  1571. },
  1572. },
  1573. plugins: [
  1574. function ({ addComponents, theme }) {
  1575. addComponents({
  1576. '.foo': {
  1577. transitionDuration: theme('transitionDuration.lol'),
  1578. },
  1579. })
  1580. },
  1581. ],
  1582. }
  1583. return run('@tailwind components', config).then((result) => {
  1584. expect(result.css).toMatchFormattedCss(css`
  1585. .foo {
  1586. transition-duration: width, height;
  1587. }
  1588. `)
  1589. })
  1590. })
  1591. test('transition-delay values are joined when retrieved using the theme function', () => {
  1592. let config = {
  1593. content: [{ raw: html`<div class="foo"></div>` }],
  1594. corePlugins: [],
  1595. theme: {
  1596. transitionDuration: {
  1597. lol: ['width', 'height'],
  1598. },
  1599. },
  1600. plugins: [
  1601. function ({ addComponents, theme }) {
  1602. addComponents({
  1603. '.foo': {
  1604. transitionDuration: theme('transitionDuration.lol'),
  1605. },
  1606. })
  1607. },
  1608. ],
  1609. }
  1610. return run('@tailwind components', config).then((result) => {
  1611. expect(result.css).toMatchFormattedCss(css`
  1612. .foo {
  1613. transition-duration: width, height;
  1614. }
  1615. `)
  1616. })
  1617. })
  1618. test('transition-timing-function values are joined when retrieved using the theme function', () => {
  1619. let config = {
  1620. content: [{ raw: html`<div class="foo"></div>` }],
  1621. corePlugins: [],
  1622. theme: {
  1623. transitionTimingFunction: {
  1624. lol: ['width', 'height'],
  1625. },
  1626. },
  1627. plugins: [
  1628. function ({ addComponents, theme }) {
  1629. addComponents({
  1630. '.foo': {
  1631. transitionTimingFunction: theme('transitionTimingFunction.lol'),
  1632. },
  1633. })
  1634. },
  1635. ],
  1636. }
  1637. return run('@tailwind components', config).then((result) => {
  1638. expect(result.css).toMatchFormattedCss(css`
  1639. .foo {
  1640. transition-timing-function: width, height;
  1641. }
  1642. `)
  1643. })
  1644. })
  1645. test('background-image values are joined when retrieved using the theme function', () => {
  1646. let config = {
  1647. content: [{ raw: html`<div class="foo"></div>` }],
  1648. corePlugins: [],
  1649. theme: {
  1650. backgroundImage: {
  1651. lol: ['width', 'height'],
  1652. },
  1653. },
  1654. plugins: [
  1655. function ({ addComponents, theme }) {
  1656. addComponents({
  1657. '.foo': {
  1658. backgroundImage: theme('backgroundImage.lol'),
  1659. },
  1660. })
  1661. },
  1662. ],
  1663. }
  1664. return run('@tailwind components', config).then((result) => {
  1665. expect(result.css).toMatchFormattedCss(css`
  1666. .foo {
  1667. background-image: width, height;
  1668. }
  1669. `)
  1670. })
  1671. })
  1672. test('background-size values are joined when retrieved using the theme function', () => {
  1673. let config = {
  1674. content: [{ raw: html`<div class="foo"></div>` }],
  1675. corePlugins: [],
  1676. theme: {
  1677. backgroundSize: {
  1678. lol: ['width', 'height'],
  1679. },
  1680. },
  1681. plugins: [
  1682. function ({ addComponents, theme }) {
  1683. addComponents({
  1684. '.foo': {
  1685. backgroundSize: theme('backgroundSize.lol'),
  1686. },
  1687. })
  1688. },
  1689. ],
  1690. }
  1691. return run('@tailwind components', config).then((result) => {
  1692. expect(result.css).toMatchFormattedCss(css`
  1693. .foo {
  1694. background-size: width, height;
  1695. }
  1696. `)
  1697. })
  1698. })
  1699. test('background-color values are joined when retrieved using the theme function', () => {
  1700. let config = {
  1701. content: [{ raw: html`<div class="foo"></div>` }],
  1702. corePlugins: [],
  1703. theme: {
  1704. backgroundColor: {
  1705. lol: ['width', 'height'],
  1706. },
  1707. },
  1708. plugins: [
  1709. function ({ addComponents, theme }) {
  1710. addComponents({
  1711. '.foo': {
  1712. backgroundColor: theme('backgroundColor.lol'),
  1713. },
  1714. })
  1715. },
  1716. ],
  1717. }
  1718. return run('@tailwind components', config).then((result) => {
  1719. expect(result.css).toMatchFormattedCss(css`
  1720. .foo {
  1721. background-color: width, height;
  1722. }
  1723. `)
  1724. })
  1725. })
  1726. test('cursor values are joined when retrieved using the theme function', () => {
  1727. let config = {
  1728. content: [{ raw: html`<div class="foo"></div>` }],
  1729. corePlugins: [],
  1730. theme: {
  1731. cursor: {
  1732. lol: ['width', 'height'],
  1733. },
  1734. },
  1735. plugins: [
  1736. function ({ addComponents, theme }) {
  1737. addComponents({
  1738. '.foo': {
  1739. cursor: theme('cursor.lol'),
  1740. },
  1741. })
  1742. },
  1743. ],
  1744. }
  1745. return run('@tailwind components', config).then((result) => {
  1746. expect(result.css).toMatchFormattedCss(css`
  1747. .foo {
  1748. cursor: width, height;
  1749. }
  1750. `)
  1751. })
  1752. })
  1753. test('animation values are joined when retrieved using the theme function', () => {
  1754. let config = {
  1755. content: [{ raw: html`<div class="foo"></div>` }],
  1756. corePlugins: [],
  1757. theme: {
  1758. animation: {
  1759. lol: ['width', 'height'],
  1760. },
  1761. },
  1762. plugins: [
  1763. function ({ addComponents, theme }) {
  1764. addComponents({
  1765. '.foo': {
  1766. animation: theme('animation.lol'),
  1767. },
  1768. })
  1769. },
  1770. ],
  1771. }
  1772. return run('@tailwind components', config).then((result) => {
  1773. expect(result.css).toMatchFormattedCss(css`
  1774. .foo {
  1775. animation: width, height;
  1776. }
  1777. `)
  1778. })
  1779. })
  1780. test('custom properties are not converted to kebab-case when added to base layer', () => {
  1781. let config = {
  1782. content: [],
  1783. plugins: [
  1784. function ({ addBase }) {
  1785. addBase({
  1786. ':root': {
  1787. '--colors-primaryThing-500': '0, 0, 255',
  1788. },
  1789. })
  1790. },
  1791. ],
  1792. }
  1793. return run('@tailwind base', config).then((result) => {
  1794. expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
  1795. })
  1796. })