basic-usage.test.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import fs from 'fs'
  2. import path from 'path'
  3. import { html, run, css, defaults } from './util/run'
  4. test('basic usage', () => {
  5. let config = {
  6. content: [path.resolve(__dirname, './basic-usage.test.html')],
  7. corePlugins: { preflight: false },
  8. }
  9. let input = css`
  10. @tailwind base;
  11. @tailwind components;
  12. @tailwind utilities;
  13. `
  14. return run(input, config).then((result) => {
  15. let expectedPath = path.resolve(__dirname, './basic-usage.test.css')
  16. let expected = fs.readFileSync(expectedPath, 'utf8')
  17. expect(result.css).toMatchFormattedCss(expected)
  18. })
  19. })
  20. test('all plugins are executed that match a candidate', () => {
  21. let config = {
  22. content: [{ raw: html`<div class="bg-green-light bg-green"></div>` }],
  23. theme: {
  24. colors: {
  25. green: {
  26. light: 'green',
  27. },
  28. },
  29. },
  30. corePlugins: { preflight: false },
  31. }
  32. let input = css`
  33. @tailwind utilities;
  34. .bg-green {
  35. /* Empty on purpose */
  36. }
  37. `
  38. return run(input, config).then((result) => {
  39. expect(result.css).toMatchFormattedCss(css`
  40. .bg-green-light {
  41. --tw-bg-opacity: 1;
  42. background-color: rgb(0 128 0 / var(--tw-bg-opacity));
  43. }
  44. .bg-green {
  45. /* Empty on purpose */
  46. }
  47. `)
  48. })
  49. })
  50. test('per-plugin colors with the same key can differ when using a custom colors object', () => {
  51. let config = {
  52. content: [
  53. {
  54. raw: html`
  55. <div class="bg-theme text-theme">This should be green text on red background.</div>
  56. `,
  57. },
  58. ],
  59. theme: {
  60. // colors & theme MUST be plain objects
  61. // If they're functions here the test passes regardless
  62. colors: {
  63. theme: {
  64. bg: 'red',
  65. text: 'green',
  66. },
  67. },
  68. extend: {
  69. textColor: {
  70. theme: {
  71. DEFAULT: 'green',
  72. },
  73. },
  74. backgroundColor: {
  75. theme: {
  76. DEFAULT: 'red',
  77. },
  78. },
  79. },
  80. },
  81. corePlugins: { preflight: false },
  82. }
  83. let input = css`
  84. @tailwind utilities;
  85. `
  86. return run(input, config).then((result) => {
  87. expect(result.css).toMatchFormattedCss(css`
  88. .bg-theme {
  89. --tw-bg-opacity: 1;
  90. background-color: rgb(255 0 0 / var(--tw-bg-opacity));
  91. }
  92. .text-theme {
  93. --tw-text-opacity: 1;
  94. color: rgb(0 128 0 / var(--tw-text-opacity));
  95. }
  96. `)
  97. })
  98. })
  99. test('default ring color can be a function', () => {
  100. function color(variable) {
  101. return function ({ opacityVariable, opacityValue }) {
  102. if (opacityValue !== undefined) {
  103. return `rgba(${variable}, ${opacityValue})`
  104. }
  105. if (opacityVariable !== undefined) {
  106. return `rgba(${variable}, var(${opacityVariable}, 1))`
  107. }
  108. return `rgb(${variable})`
  109. }
  110. }
  111. let config = {
  112. content: [
  113. {
  114. raw: html` <div class="ring"></div> `,
  115. },
  116. ],
  117. theme: {
  118. extend: {
  119. ringColor: {
  120. DEFAULT: color('var(--red)'),
  121. },
  122. },
  123. },
  124. plugins: [],
  125. corePlugins: { preflight: false },
  126. }
  127. let input = css`
  128. @tailwind base;
  129. @tailwind components;
  130. @tailwind utilities;
  131. `
  132. return run(input, config).then((result) => {
  133. expect(result.css).toMatchFormattedCss(css`
  134. ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
  135. .ring {
  136. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  137. var(--tw-ring-offset-color);
  138. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  139. var(--tw-ring-color);
  140. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  141. }
  142. `)
  143. })
  144. })
  145. it('falsy config values still work', () => {
  146. let config = {
  147. content: [{ raw: html`<div class="inset-0"></div>` }],
  148. theme: {
  149. inset: {
  150. 0: 0,
  151. },
  152. },
  153. plugins: [],
  154. corePlugins: { preflight: false },
  155. }
  156. let input = css`
  157. @tailwind utilities;
  158. `
  159. return run(input, config).then((result) => {
  160. expect(result.css).toMatchFormattedCss(css`
  161. .inset-0 {
  162. top: 0;
  163. right: 0;
  164. bottom: 0;
  165. left: 0;
  166. }
  167. `)
  168. })
  169. })
  170. it('shadows support values without a leading zero', () => {
  171. let config = {
  172. content: [{ raw: html`<div class="shadow-one shadow-two"></div>` }],
  173. theme: {
  174. boxShadow: {
  175. one: '0.5rem 0.5rem 0.5rem #0005',
  176. two: '.5rem .5rem .5rem #0005',
  177. },
  178. },
  179. plugins: [],
  180. corePlugins: { preflight: false },
  181. }
  182. let input = css`
  183. @tailwind utilities;
  184. `
  185. return run(input, config).then((result) => {
  186. expect(result.css).toMatchFormattedCss(css`
  187. .shadow-one {
  188. --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
  189. --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
  190. box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
  191. var(--tw-shadow);
  192. }
  193. .shadow-two {
  194. --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
  195. --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
  196. box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
  197. var(--tw-shadow);
  198. }
  199. `)
  200. })
  201. })
  202. it('can scan extremely long classes without crashing', () => {
  203. let val = 'cols-' + '-a'.repeat(65536)
  204. let config = {
  205. content: [{ raw: html`<div class="${val}"></div>` }],
  206. corePlugins: { preflight: false },
  207. }
  208. let input = css`
  209. @tailwind utilities;
  210. `
  211. return run(input, config).then((result) => {
  212. expect(result.css).toMatchFormattedCss(css``)
  213. })
  214. })
  215. it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
  216. let config = {
  217. content: [{ raw: html`underline focus:*` }],
  218. corePlugins: { preflight: false },
  219. }
  220. let input = css`
  221. @tailwind base;
  222. @tailwind components;
  223. @tailwind utilities;
  224. * {
  225. color: red;
  226. }
  227. .combined,
  228. * {
  229. text-align: center;
  230. }
  231. @layer base {
  232. * {
  233. color: blue;
  234. }
  235. .combined,
  236. * {
  237. color: red;
  238. }
  239. }
  240. `
  241. return run(input, config).then((result) => {
  242. expect(result.css).toMatchFormattedCss(css`
  243. * {
  244. color: blue;
  245. }
  246. .combined,
  247. * {
  248. color: red;
  249. }
  250. ${defaults}
  251. .underline {
  252. text-decoration-line: underline;
  253. }
  254. * {
  255. color: red;
  256. }
  257. .combined,
  258. * {
  259. text-align: center;
  260. }
  261. `)
  262. })
  263. })
  264. it('can parse box shadows with variables', () => {
  265. let config = {
  266. content: [{ raw: html`<div class="shadow-lg"></div>` }],
  267. theme: {
  268. boxShadow: {
  269. lg: 'var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
  270. },
  271. },
  272. corePlugins: { preflight: false },
  273. }
  274. let input = css`
  275. @tailwind utilities;
  276. `
  277. return run(input, config).then((result) => {
  278. expect(result.css).toMatchFormattedCss(css`
  279. .shadow-lg {
  280. --tw-shadow: var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0);
  281. --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
  282. 0 0 1px var(--tw-shadow-color);
  283. box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
  284. var(--tw-shadow);
  285. }
  286. `)
  287. })
  288. })
  289. it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
  290. let config = {
  291. content: [{ raw: html`<div></div>` }],
  292. corePlugins: { preflight: false },
  293. }
  294. let input = css`
  295. @tailwind components;
  296. @layer components {
  297. div:not(.unknown-class) {
  298. color: red;
  299. }
  300. }
  301. `
  302. return run(input, config).then((result) => {
  303. expect(result.css).toMatchFormattedCss(css`
  304. div:not(.unknown-class) {
  305. color: red;
  306. }
  307. `)
  308. })
  309. })
  310. it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
  311. let config = {
  312. content: [
  313. {
  314. raw: html`<div
  315. class="bg-[url('/images/one-two-three.png'),linear-gradient(to_right,_#eeeeee,_#000000)]"
  316. ></div>`,
  317. },
  318. ],
  319. corePlugins: { preflight: false },
  320. }
  321. let input = css`
  322. @tailwind utilities;
  323. `
  324. return run(input, config).then((result) => {
  325. expect(result.css).toMatchFormattedCss(css`
  326. .bg-\[url\(\'\/images\/one-two-three\.png\'\)\2c
  327. linear-gradient\(to_right\2c
  328. _\#eeeeee\2c
  329. _\#000000\)\] {
  330. background-image: url('/images/one-two-three.png'),
  331. linear-gradient(to right, #eeeeee, #000000);
  332. }
  333. `)
  334. })
  335. })
  336. it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
  337. let config = {
  338. content: [{ raw: html`<div class="ring"></div>` }],
  339. corePlugins: { preflight: false },
  340. }
  341. let input = css`
  342. @tailwind base;
  343. @tailwind utilities;
  344. `
  345. return run(input, config).then((result) => {
  346. expect(result.css).toMatchFormattedCss(css`
  347. ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.5)' })}
  348. .ring {
  349. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  350. var(--tw-ring-offset-color);
  351. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  352. var(--tw-ring-color);
  353. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  354. }
  355. `)
  356. })
  357. })
  358. it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
  359. let config = {
  360. content: [{ raw: html`<div class="ring"></div>` }],
  361. corePlugins: { preflight: false },
  362. theme: {
  363. ringOpacity: {
  364. DEFAULT: 0.75,
  365. },
  366. },
  367. }
  368. let input = css`
  369. @tailwind base;
  370. @tailwind utilities;
  371. `
  372. return run(input, config).then((result) => {
  373. expect(result.css).toMatchFormattedCss(css`
  374. ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.75)' })}
  375. .ring {
  376. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  377. var(--tw-ring-offset-color);
  378. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  379. var(--tw-ring-color);
  380. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  381. }
  382. `)
  383. })
  384. })
  385. it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
  386. let config = {
  387. content: [{ raw: html`<div class="ring"></div>` }],
  388. corePlugins: { preflight: false },
  389. theme: {
  390. ringColor: {
  391. DEFAULT: '#ff7f7f',
  392. },
  393. },
  394. }
  395. let input = css`
  396. @tailwind base;
  397. @tailwind utilities;
  398. `
  399. return run(input, config).then((result) => {
  400. expect(result.css).toMatchFormattedCss(css`
  401. ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
  402. .ring {
  403. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  404. var(--tw-ring-offset-color);
  405. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  406. var(--tw-ring-color);
  407. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  408. }
  409. `)
  410. })
  411. })
  412. it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
  413. let config = {
  414. content: [{ raw: html`<div class="ring"></div>` }],
  415. corePlugins: { preflight: false },
  416. theme: {
  417. ringColor: {
  418. DEFAULT: '#ff7f7f00',
  419. },
  420. },
  421. }
  422. let input = css`
  423. @tailwind base;
  424. @tailwind utilities;
  425. `
  426. return run(input, config).then((result) => {
  427. expect(result.css).toMatchFormattedCss(css`
  428. ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
  429. .ring {
  430. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  431. var(--tw-ring-offset-color);
  432. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  433. var(--tw-ring-color);
  434. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  435. }
  436. `)
  437. })
  438. })
  439. it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
  440. let config = {
  441. future: { respectDefaultRingColorOpacity: true },
  442. content: [{ raw: html`<div class="ring"></div>` }],
  443. corePlugins: { preflight: false },
  444. }
  445. let input = css`
  446. @tailwind base;
  447. @tailwind utilities;
  448. `
  449. return run(input, config).then((result) => {
  450. expect(result.css).toMatchFormattedCss(css`
  451. ${defaults({ defaultRingColor: '#3b82f67f' })}
  452. .ring {
  453. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  454. var(--tw-ring-offset-color);
  455. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  456. var(--tw-ring-color);
  457. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  458. }
  459. `)
  460. })
  461. })
  462. it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
  463. let config = {
  464. future: { respectDefaultRingColorOpacity: true },
  465. content: [{ raw: html`<div class="ring"></div>` }],
  466. corePlugins: { preflight: false },
  467. theme: {
  468. ringOpacity: {
  469. DEFAULT: 0.75,
  470. },
  471. },
  472. }
  473. let input = css`
  474. @tailwind base;
  475. @tailwind utilities;
  476. `
  477. return run(input, config).then((result) => {
  478. expect(result.css).toMatchFormattedCss(css`
  479. ${defaults({ defaultRingColor: '#3b82f67f' })}
  480. .ring {
  481. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  482. var(--tw-ring-offset-color);
  483. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  484. var(--tw-ring-color);
  485. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  486. }
  487. `)
  488. })
  489. })
  490. it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
  491. let config = {
  492. future: { respectDefaultRingColorOpacity: true },
  493. content: [{ raw: html`<div class="ring"></div>` }],
  494. corePlugins: { preflight: false },
  495. theme: {
  496. ringColor: {
  497. DEFAULT: '#ff7f7f',
  498. },
  499. },
  500. }
  501. let input = css`
  502. @tailwind base;
  503. @tailwind utilities;
  504. `
  505. return run(input, config).then((result) => {
  506. expect(result.css).toMatchFormattedCss(css`
  507. ${defaults({ defaultRingColor: '#ff7f7f' })}
  508. .ring {
  509. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  510. var(--tw-ring-offset-color);
  511. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  512. var(--tw-ring-color);
  513. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  514. }
  515. `)
  516. })
  517. })
  518. it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
  519. let config = {
  520. future: { respectDefaultRingColorOpacity: true },
  521. content: [{ raw: html`<div class="ring"></div>` }],
  522. corePlugins: { preflight: false },
  523. theme: {
  524. ringColor: {
  525. DEFAULT: '#ff7f7f00',
  526. },
  527. },
  528. }
  529. let input = css`
  530. @tailwind base;
  531. @tailwind utilities;
  532. `
  533. return run(input, config).then((result) => {
  534. expect(result.css).toMatchFormattedCss(css`
  535. ${defaults({ defaultRingColor: '#ff7f7f00' })}
  536. .ring {
  537. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  538. var(--tw-ring-offset-color);
  539. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  540. var(--tw-ring-color);
  541. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  542. }
  543. `)
  544. })
  545. })
  546. it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
  547. let config = {
  548. content: [{ raw: html`<div class="ring-opacity"></div>` }],
  549. corePlugins: { preflight: false },
  550. theme: {
  551. ringOpacity: {
  552. DEFAULT: '0.33',
  553. },
  554. },
  555. }
  556. let input = css`
  557. @tailwind utilities;
  558. `
  559. return run(input, config).then((result) => {
  560. expect(result.css).toMatchFormattedCss(css``)
  561. })
  562. })
  563. it('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
  564. let config = {
  565. future: { respectDefaultRingColorOpacity: true },
  566. content: [{ raw: html`<div class="ring-opacity"></div>` }],
  567. corePlugins: { preflight: false },
  568. theme: {
  569. ringOpacity: {
  570. DEFAULT: '0.33',
  571. },
  572. },
  573. }
  574. let input = css`
  575. @tailwind utilities;
  576. `
  577. return run(input, config).then((result) => {
  578. expect(result.css).toMatchFormattedCss(css`
  579. .ring-opacity {
  580. --tw-ring-opacity: 0.33;
  581. }
  582. `)
  583. })
  584. })
  585. it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
  586. let config = {
  587. future: { respectDefaultRingColorOpacity: true },
  588. content: [{ raw: html`<div class="ring ring-blue-500"></div>` }],
  589. corePlugins: { preflight: false },
  590. }
  591. let input = css`
  592. @tailwind utilities;
  593. `
  594. return run(input, config).then((result) => {
  595. expect(result.css).toMatchFormattedCss(css`
  596. .ring {
  597. --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
  598. var(--tw-ring-offset-color);
  599. --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
  600. var(--tw-ring-color);
  601. box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
  602. }
  603. .ring-blue-500 {
  604. --tw-ring-opacity: 1;
  605. --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
  606. }
  607. `)
  608. })
  609. })