evaluateTailwindFunctions.test.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419
  1. import fs from 'fs'
  2. import path from 'path'
  3. import postcss from 'postcss'
  4. import plugin from '../src/lib/evaluateTailwindFunctions'
  5. import { run as runFull, html, css } from './util/run'
  6. function run(input, opts = {}) {
  7. return postcss([
  8. plugin({
  9. tailwindConfig: opts,
  10. markInvalidUtilityNode() {
  11. // no op
  12. },
  13. }),
  14. ]).process(input, { from: undefined })
  15. }
  16. test('it looks up values in the theme using dot notation', () => {
  17. let input = css`
  18. .banana {
  19. color: theme('colors.yellow');
  20. }
  21. `
  22. let output = css`
  23. .banana {
  24. color: #f7cc50;
  25. }
  26. `
  27. return run(input, {
  28. theme: {
  29. colors: {
  30. yellow: '#f7cc50',
  31. },
  32. },
  33. }).then((result) => {
  34. expect(result.css).toEqual(output)
  35. expect(result.warnings().length).toBe(0)
  36. })
  37. })
  38. test('it looks up values in the theme using bracket notation', () => {
  39. let input = css`
  40. .banana {
  41. color: theme('colors[yellow]');
  42. }
  43. `
  44. let output = css`
  45. .banana {
  46. color: #f7cc50;
  47. }
  48. `
  49. return run(input, {
  50. theme: {
  51. colors: {
  52. yellow: '#f7cc50',
  53. },
  54. },
  55. }).then((result) => {
  56. expect(result.css).toEqual(output)
  57. expect(result.warnings().length).toBe(0)
  58. })
  59. })
  60. test('it looks up values in the theme using consecutive bracket notation', () => {
  61. let input = css`
  62. .banana {
  63. color: theme('colors[yellow][100]');
  64. }
  65. `
  66. let output = css`
  67. .banana {
  68. color: #f7cc50;
  69. }
  70. `
  71. return run(input, {
  72. theme: {
  73. colors: {
  74. yellow: {
  75. 100: '#f7cc50',
  76. },
  77. },
  78. },
  79. }).then((result) => {
  80. expect(result.css).toEqual(output)
  81. expect(result.warnings().length).toBe(0)
  82. })
  83. })
  84. test('it looks up values in the theme using bracket notation that have dots in them', () => {
  85. let input = css`
  86. .banana {
  87. padding-top: theme('spacing[1.5]');
  88. }
  89. `
  90. let output = css`
  91. .banana {
  92. padding-top: 0.375rem;
  93. }
  94. `
  95. return run(input, {
  96. theme: {
  97. spacing: {
  98. '1.5': '0.375rem',
  99. },
  100. },
  101. }).then((result) => {
  102. expect(result.css).toEqual(output)
  103. expect(result.warnings().length).toBe(0)
  104. })
  105. })
  106. test('theme with mismatched brackets throws an error ', async () => {
  107. let config = {
  108. theme: {
  109. spacing: {
  110. '1.5': '0.375rem',
  111. },
  112. },
  113. }
  114. let input = (path) => css`
  115. .banana {
  116. padding-top: theme('${path}');
  117. }
  118. `
  119. await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
  120. `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
  121. )
  122. await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
  123. `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
  124. )
  125. await expect(run(input('spacing[a['), config)).rejects.toThrowError(
  126. `Path is invalid. Has unbalanced brackets: spacing[a[`
  127. )
  128. })
  129. test('color can be a function', () => {
  130. let input = css`
  131. .backgroundColor {
  132. color: theme('backgroundColor.fn');
  133. }
  134. .borderColor {
  135. color: theme('borderColor.fn');
  136. }
  137. .caretColor {
  138. color: theme('caretColor.fn');
  139. }
  140. .colors {
  141. color: theme('colors.fn');
  142. }
  143. .divideColor {
  144. color: theme('divideColor.fn');
  145. }
  146. .fill {
  147. color: theme('fill.fn');
  148. }
  149. .gradientColorStops {
  150. color: theme('gradientColorStops.fn');
  151. }
  152. .placeholderColor {
  153. color: theme('placeholderColor.fn');
  154. }
  155. .ringColor {
  156. color: theme('ringColor.fn');
  157. }
  158. .ringOffsetColor {
  159. color: theme('ringOffsetColor.fn');
  160. }
  161. .stroke {
  162. color: theme('stroke.fn');
  163. }
  164. .textColor {
  165. color: theme('textColor.fn');
  166. }
  167. `
  168. let output = css`
  169. .backgroundColor {
  170. color: #f00;
  171. }
  172. .borderColor {
  173. color: #f00;
  174. }
  175. .caretColor {
  176. color: #f00;
  177. }
  178. .colors {
  179. color: #f00;
  180. }
  181. .divideColor {
  182. color: #f00;
  183. }
  184. .fill {
  185. color: #f00;
  186. }
  187. .gradientColorStops {
  188. color: #f00;
  189. }
  190. .placeholderColor {
  191. color: #f00;
  192. }
  193. .ringColor {
  194. color: #f00;
  195. }
  196. .ringOffsetColor {
  197. color: #f00;
  198. }
  199. .stroke {
  200. color: #f00;
  201. }
  202. .textColor {
  203. color: #f00;
  204. }
  205. `
  206. let fn = () => `#f00`
  207. return run(input, {
  208. theme: {
  209. backgroundColor: { fn },
  210. borderColor: { fn },
  211. caretColor: { fn },
  212. colors: { fn },
  213. divideColor: { fn },
  214. fill: { fn },
  215. gradientColorStops: { fn },
  216. placeholderColor: { fn },
  217. ringColor: { fn },
  218. ringOffsetColor: { fn },
  219. stroke: { fn },
  220. textColor: { fn },
  221. },
  222. }).then((result) => {
  223. expect(result.css).toEqual(output)
  224. expect(result.warnings().length).toBe(0)
  225. })
  226. })
  227. test('quotes are optional around the lookup path', () => {
  228. let input = css`
  229. .banana {
  230. color: theme(colors.yellow);
  231. }
  232. `
  233. let output = css`
  234. .banana {
  235. color: #f7cc50;
  236. }
  237. `
  238. return run(input, {
  239. theme: {
  240. colors: {
  241. yellow: '#f7cc50',
  242. },
  243. },
  244. }).then((result) => {
  245. expect(result.css).toEqual(output)
  246. expect(result.warnings().length).toBe(0)
  247. })
  248. })
  249. test('a default value can be provided', () => {
  250. let input = css`
  251. .cookieMonster {
  252. color: theme('colors.blue', #0000ff);
  253. }
  254. `
  255. let output = css`
  256. .cookieMonster {
  257. color: #0000ff;
  258. }
  259. `
  260. return run(input, {
  261. theme: {
  262. colors: {
  263. yellow: '#f7cc50',
  264. },
  265. },
  266. }).then((result) => {
  267. expect(result.css).toEqual(output)
  268. expect(result.warnings().length).toBe(0)
  269. })
  270. })
  271. test('the default value can use the theme function', () => {
  272. let input = css`
  273. .cookieMonster {
  274. color: theme('colors.blue', theme('colors.yellow'));
  275. }
  276. `
  277. let output = css`
  278. .cookieMonster {
  279. color: #f7cc50;
  280. }
  281. `
  282. return run(input, {
  283. theme: {
  284. colors: {
  285. yellow: '#f7cc50',
  286. },
  287. },
  288. }).then((result) => {
  289. expect(result.css).toEqual(output)
  290. expect(result.warnings().length).toBe(0)
  291. })
  292. })
  293. test('quotes are preserved around default values', () => {
  294. let input = css`
  295. .heading {
  296. font-family: theme('fontFamily.sans', 'Helvetica Neue');
  297. }
  298. `
  299. let output = css`
  300. .heading {
  301. font-family: 'Helvetica Neue';
  302. }
  303. `
  304. return run(input, {
  305. theme: {
  306. fontFamily: {
  307. serif: 'Constantia',
  308. },
  309. },
  310. }).then((result) => {
  311. expect(result.css).toEqual(output)
  312. expect(result.warnings().length).toBe(0)
  313. })
  314. })
  315. test('an unquoted list is valid as a default value', () => {
  316. let input = css`
  317. .heading {
  318. font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
  319. }
  320. `
  321. let output = css`
  322. .heading {
  323. font-family: Helvetica, Arial, sans-serif;
  324. }
  325. `
  326. return run(input, {
  327. theme: {
  328. fontFamily: {
  329. serif: 'Constantia',
  330. },
  331. },
  332. }).then((result) => {
  333. expect(result.css).toEqual(output)
  334. expect(result.warnings().length).toBe(0)
  335. })
  336. })
  337. test('a missing root theme value throws', () => {
  338. let input = css`
  339. .heading {
  340. color: theme('colours.gray.100');
  341. }
  342. `
  343. return expect(
  344. run(input, {
  345. theme: {
  346. colors: {
  347. yellow: '#f7cc50',
  348. },
  349. },
  350. })
  351. ).rejects.toThrowError(
  352. `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'`
  353. )
  354. })
  355. test('a missing nested theme property throws', () => {
  356. let input = css`
  357. .heading {
  358. color: theme('colors.red');
  359. }
  360. `
  361. return expect(
  362. run(input, {
  363. theme: {
  364. colors: {
  365. blue: 'blue',
  366. yellow: '#f7cc50',
  367. },
  368. },
  369. })
  370. ).rejects.toThrowError(
  371. `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'`
  372. )
  373. })
  374. test('a missing nested theme property with a close alternative throws with a suggestion', () => {
  375. let input = css`
  376. .heading {
  377. color: theme('colors.yellw');
  378. }
  379. `
  380. return expect(
  381. run(input, {
  382. theme: {
  383. colors: {
  384. yellow: '#f7cc50',
  385. },
  386. },
  387. })
  388. ).rejects.toThrowError(
  389. `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?`
  390. )
  391. })
  392. test('a path through a non-object throws', () => {
  393. let input = css`
  394. .heading {
  395. color: theme('colors.yellow.100');
  396. }
  397. `
  398. return expect(
  399. run(input, {
  400. theme: {
  401. colors: {
  402. yellow: '#f7cc50',
  403. },
  404. },
  405. })
  406. ).rejects.toThrowError(
  407. `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.`
  408. )
  409. })
  410. test('a path which exists but is not a string throws', () => {
  411. let input = css`
  412. .heading {
  413. color: theme('colors.yellow');
  414. }
  415. `
  416. return expect(
  417. run(input, {
  418. theme: {
  419. colors: {
  420. yellow: Symbol(),
  421. },
  422. },
  423. })
  424. ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`)
  425. })
  426. test('a path which exists but is invalid throws', () => {
  427. let input = css`
  428. .heading {
  429. color: theme('colors');
  430. }
  431. `
  432. return expect(
  433. run(input, {
  434. theme: {
  435. colors: {},
  436. },
  437. })
  438. ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`)
  439. })
  440. test('a path which is an object throws with a suggested key', () => {
  441. let input = css`
  442. .heading {
  443. color: theme('colors');
  444. }
  445. `
  446. return expect(
  447. run(input, {
  448. theme: {
  449. colors: {
  450. yellow: '#f7cc50',
  451. },
  452. },
  453. })
  454. ).rejects.toThrowError(
  455. `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?`
  456. )
  457. })
  458. test('array values are joined by default', () => {
  459. let input = css`
  460. .heading {
  461. font-family: theme('fontFamily.sans');
  462. }
  463. `
  464. let output = css`
  465. .heading {
  466. font-family: Inter, Helvetica, sans-serif;
  467. }
  468. `
  469. return run(input, {
  470. theme: {
  471. fontFamily: {
  472. sans: ['Inter', 'Helvetica', 'sans-serif'],
  473. },
  474. },
  475. }).then((result) => {
  476. expect(result.css).toEqual(output)
  477. expect(result.warnings().length).toBe(0)
  478. })
  479. })
  480. test('font sizes are retrieved without default line-heights or letter-spacing', () => {
  481. let input = css`
  482. .heading-1 {
  483. font-size: theme('fontSize.lg');
  484. }
  485. .heading-2 {
  486. font-size: theme('fontSize.xl');
  487. }
  488. `
  489. let output = css`
  490. .heading-1 {
  491. font-size: 20px;
  492. }
  493. .heading-2 {
  494. font-size: 24px;
  495. }
  496. `
  497. return run(input, {
  498. theme: {
  499. fontSize: {
  500. lg: ['20px', '28px'],
  501. xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }],
  502. },
  503. },
  504. }).then((result) => {
  505. expect(result.css).toMatchFormattedCss(output)
  506. expect(result.warnings().length).toBe(0)
  507. })
  508. })
  509. test('outlines are retrieved without default outline-offset', () => {
  510. let input = css`
  511. .element {
  512. outline: theme('outline.black');
  513. }
  514. `
  515. return run(input, {
  516. theme: {
  517. outline: {
  518. black: ['2px dotted black', '4px'],
  519. },
  520. },
  521. }).then((result) => {
  522. expect(result.css).toMatchFormattedCss(css`
  523. .element {
  524. outline: 2px dotted black;
  525. }
  526. `)
  527. expect(result.warnings().length).toBe(0)
  528. })
  529. })
  530. test('font-family values are retrieved without font-variation-settings', () => {
  531. let input = css`
  532. .heading-1 {
  533. font-family: theme('fontFamily.sans');
  534. }
  535. .heading-2 {
  536. font-family: theme('fontFamily.serif');
  537. }
  538. .heading-3 {
  539. font-family: theme('fontFamily.mono');
  540. }
  541. `
  542. let output = css`
  543. .heading-1 {
  544. font-family: Inter;
  545. }
  546. .heading-2 {
  547. font-family: Times, serif;
  548. }
  549. .heading-3 {
  550. font-family: Menlo, monospace;
  551. }
  552. `
  553. return run(input, {
  554. theme: {
  555. fontFamily: {
  556. sans: ['Inter', { fontVariationSettings: '"opsz" 32' }],
  557. serif: [['Times', 'serif'], { fontVariationSettings: '"opsz" 32' }],
  558. mono: ['Menlo, monospace', { fontVariationSettings: '"opsz" 32' }],
  559. },
  560. },
  561. }).then((result) => {
  562. expect(result.css).toMatchFormattedCss(output)
  563. expect(result.warnings().length).toBe(0)
  564. })
  565. })
  566. test('font-variation-settings values can be retrieved', () => {
  567. let input = css`
  568. .heading {
  569. font-family: theme('fontFamily.sans');
  570. font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings');
  571. }
  572. `
  573. let output = css`
  574. .heading {
  575. font-family: Inter;
  576. font-variation-settings: 'opsz' 32;
  577. }
  578. `
  579. return run(input, {
  580. theme: {
  581. fontFamily: {
  582. sans: ['Inter', { fontVariationSettings: "'opsz' 32" }],
  583. },
  584. },
  585. }).then((result) => {
  586. expect(result.css).toMatchFormattedCss(output)
  587. expect(result.warnings().length).toBe(0)
  588. })
  589. })
  590. test('font-family values are joined when an array', () => {
  591. let input = css`
  592. .element {
  593. font-family: theme('fontFamily.sans');
  594. }
  595. `
  596. let output = css`
  597. .element {
  598. font-family: Helvetica, Arial, sans-serif;
  599. }
  600. `
  601. return run(input, {
  602. theme: {
  603. fontFamily: {
  604. sans: ['Helvetica', 'Arial', 'sans-serif'],
  605. },
  606. },
  607. }).then((result) => {
  608. expect(result.css).toMatchFormattedCss(output)
  609. expect(result.warnings().length).toBe(0)
  610. })
  611. })
  612. test('font-family values are retrieved without font-feature-settings', () => {
  613. let input = css`
  614. .heading-1 {
  615. font-family: theme('fontFamily.sans');
  616. }
  617. .heading-2 {
  618. font-family: theme('fontFamily.serif');
  619. }
  620. .heading-3 {
  621. font-family: theme('fontFamily.mono');
  622. }
  623. `
  624. let output = css`
  625. .heading-1 {
  626. font-family: Inter;
  627. }
  628. .heading-2 {
  629. font-family: Times, serif;
  630. }
  631. .heading-3 {
  632. font-family: Menlo, monospace;
  633. }
  634. `
  635. return run(input, {
  636. theme: {
  637. fontFamily: {
  638. sans: ['Inter', { fontFeatureSettings: '"cv11"' }],
  639. serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }],
  640. mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }],
  641. },
  642. },
  643. }).then((result) => {
  644. expect(result.css).toMatchFormattedCss(output)
  645. expect(result.warnings().length).toBe(0)
  646. })
  647. })
  648. test('font-feature-settings values can be retrieved', () => {
  649. let input = css`
  650. .heading {
  651. font-family: theme('fontFamily.sans');
  652. font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings');
  653. }
  654. `
  655. return run(input, {
  656. theme: {
  657. fontFamily: {
  658. sans: ['Inter', { fontFeatureSettings: "'cv11'" }],
  659. },
  660. },
  661. }).then((result) => {
  662. expect(result.css).toMatchFormattedCss(css`
  663. .heading {
  664. font-family: Inter;
  665. font-feature-settings: 'cv11';
  666. }
  667. `)
  668. expect(result.warnings().length).toBe(0)
  669. })
  670. })
  671. test('box-shadow values are joined when an array', () => {
  672. let input = css`
  673. .element {
  674. box-shadow: theme('boxShadow.wtf');
  675. }
  676. `
  677. return run(input, {
  678. theme: {
  679. boxShadow: {
  680. wtf: ['0 0 2px black', '1px 2px 3px white'],
  681. },
  682. },
  683. }).then((result) => {
  684. expect(result.css).toMatchFormattedCss(css`
  685. .element {
  686. box-shadow: 0 0 2px black, 1px 2px 3px white;
  687. }
  688. `)
  689. expect(result.warnings().length).toBe(0)
  690. })
  691. })
  692. test('transition-property values are joined when an array', () => {
  693. let input = css`
  694. .element {
  695. transition-property: theme('transitionProperty.colors');
  696. }
  697. `
  698. let output = css`
  699. .element {
  700. transition-property: color, fill;
  701. }
  702. `
  703. return run(input, {
  704. theme: {
  705. transitionProperty: {
  706. colors: ['color', 'fill'],
  707. },
  708. },
  709. }).then((result) => {
  710. expect(result.css).toMatchFormattedCss(output)
  711. expect(result.warnings().length).toBe(0)
  712. })
  713. })
  714. test('transition-duration values are joined when an array', () => {
  715. let input = css`
  716. .element {
  717. transition-duration: theme('transitionDuration.lol');
  718. }
  719. `
  720. let output = css`
  721. .element {
  722. transition-duration: 1s, 2s;
  723. }
  724. `
  725. return run(input, {
  726. theme: {
  727. transitionDuration: {
  728. lol: ['1s', '2s'],
  729. },
  730. },
  731. }).then((result) => {
  732. expect(result.css).toMatchFormattedCss(output)
  733. expect(result.warnings().length).toBe(0)
  734. })
  735. })
  736. test('basic screen function calls are expanded', () => {
  737. let input = css`
  738. @media screen(sm) {
  739. .foo {
  740. color: red;
  741. }
  742. }
  743. `
  744. return run(input, {
  745. theme: { screens: { sm: '600px' } },
  746. }).then((result) => {
  747. expect(result.css).toMatchFormattedCss(css`
  748. @media (min-width: 600px) {
  749. .foo {
  750. color: red;
  751. }
  752. }
  753. `)
  754. expect(result.warnings().length).toBe(0)
  755. })
  756. })
  757. test('screen function supports max-width screens', () => {
  758. let input = css`
  759. @media screen(sm) {
  760. .foo {
  761. color: red;
  762. }
  763. }
  764. `
  765. let output = css`
  766. @media (max-width: 600px) {
  767. .foo {
  768. color: red;
  769. }
  770. }
  771. `
  772. return run(input, {
  773. theme: { screens: { sm: { max: '600px' } } },
  774. }).then((result) => {
  775. expect(result.css).toMatchFormattedCss(output)
  776. expect(result.warnings().length).toBe(0)
  777. })
  778. })
  779. test('screen function supports min-width screens', () => {
  780. let input = css`
  781. @media screen(sm) {
  782. .foo {
  783. color: red;
  784. }
  785. }
  786. `
  787. return run(input, {
  788. theme: { screens: { sm: { min: '600px' } } },
  789. }).then((result) => {
  790. expect(result.css).toMatchFormattedCss(css`
  791. @media (min-width: 600px) {
  792. .foo {
  793. color: red;
  794. }
  795. }
  796. `)
  797. expect(result.warnings().length).toBe(0)
  798. })
  799. })
  800. test('screen function supports min-width and max-width screens', () => {
  801. let input = css`
  802. @media screen(sm) {
  803. .foo {
  804. color: red;
  805. }
  806. }
  807. `
  808. return run(input, {
  809. theme: { screens: { sm: { min: '600px', max: '700px' } } },
  810. }).then((result) => {
  811. expect(result.css).toMatchFormattedCss(css`
  812. @media (min-width: 600px) and (max-width: 700px) {
  813. .foo {
  814. color: red;
  815. }
  816. }
  817. `)
  818. expect(result.warnings().length).toBe(0)
  819. })
  820. })
  821. test('screen function supports raw screens', () => {
  822. let input = css`
  823. @media screen(mono) {
  824. .foo {
  825. color: red;
  826. }
  827. }
  828. `
  829. let output = css`
  830. @media monochrome {
  831. .foo {
  832. color: red;
  833. }
  834. }
  835. `
  836. return run(input, {
  837. theme: { screens: { mono: { raw: 'monochrome' } } },
  838. }).then((result) => {
  839. expect(result.css).toMatchFormattedCss(output)
  840. expect(result.warnings().length).toBe(0)
  841. })
  842. })
  843. test('screen arguments can be quoted', () => {
  844. let input = css`
  845. @media screen('sm') {
  846. .foo {
  847. color: red;
  848. }
  849. }
  850. `
  851. return run(input, {
  852. theme: { screens: { sm: '600px' } },
  853. }).then((result) => {
  854. expect(result.css).toMatchFormattedCss(css`
  855. @media (min-width: 600px) {
  856. .foo {
  857. color: red;
  858. }
  859. }
  860. `)
  861. expect(result.warnings().length).toBe(0)
  862. })
  863. })
  864. test('Theme function can extract alpha values for colors (1)', () => {
  865. let input = css`
  866. .foo {
  867. color: theme(colors.blue.500 / 50%);
  868. }
  869. `
  870. return run(input, {
  871. theme: {
  872. colors: { blue: { 500: '#3b82f6' } },
  873. },
  874. }).then((result) => {
  875. expect(result.css).toMatchFormattedCss(css`
  876. .foo {
  877. color: rgb(59 130 246 / 50%);
  878. }
  879. `)
  880. expect(result.warnings().length).toBe(0)
  881. })
  882. })
  883. test('Theme function can extract alpha values for colors (2)', () => {
  884. let input = css`
  885. .foo {
  886. color: theme(colors.blue.500 / 0.5);
  887. }
  888. `
  889. return run(input, {
  890. theme: {
  891. colors: { blue: { 500: '#3b82f6' } },
  892. },
  893. }).then((result) => {
  894. expect(result.css).toMatchFormattedCss(css`
  895. .foo {
  896. color: rgb(59 130 246 / 0.5);
  897. }
  898. `)
  899. expect(result.warnings().length).toBe(0)
  900. })
  901. })
  902. test('Theme function can extract alpha values for colors (3)', () => {
  903. let input = css`
  904. .foo {
  905. color: theme(colors.blue.500 / var(--my-alpha));
  906. }
  907. `
  908. let output = css`
  909. .foo {
  910. color: rgb(59 130 246 / var(--my-alpha));
  911. }
  912. `
  913. return run(input, {
  914. theme: {
  915. colors: { blue: { 500: '#3b82f6' } },
  916. },
  917. }).then((result) => {
  918. expect(result.css).toMatchFormattedCss(output)
  919. expect(result.warnings().length).toBe(0)
  920. })
  921. })
  922. test('Theme function can extract alpha values for colors (4)', () => {
  923. let input = css`
  924. .foo {
  925. color: theme(colors.blue.500 / 50%);
  926. }
  927. `
  928. return run(input, {
  929. theme: {
  930. colors: {
  931. blue: { 500: 'hsl(217, 91%, 60%)' },
  932. },
  933. },
  934. }).then((result) => {
  935. expect(result.css).toMatchFormattedCss(css`
  936. .foo {
  937. color: hsl(217 91% 60% / 50%);
  938. }
  939. `)
  940. expect(result.warnings().length).toBe(0)
  941. })
  942. })
  943. test('Theme function can extract alpha values for colors (5)', () => {
  944. let input = css`
  945. .foo {
  946. color: theme(colors.blue.500 / 0.5);
  947. }
  948. `
  949. return run(input, {
  950. theme: {
  951. colors: {
  952. blue: { 500: 'hsl(217, 91%, 60%)' },
  953. },
  954. },
  955. }).then((result) => {
  956. expect(result.css).toMatchFormattedCss(css`
  957. .foo {
  958. color: hsl(217 91% 60% / 0.5);
  959. }
  960. `)
  961. expect(result.warnings().length).toBe(0)
  962. })
  963. })
  964. test('Theme function can extract alpha values for colors (6)', () => {
  965. let input = css`
  966. .foo {
  967. color: theme(colors.blue.500 / var(--my-alpha));
  968. }
  969. `
  970. let output = css`
  971. .foo {
  972. color: hsl(217 91% 60% / var(--my-alpha));
  973. }
  974. `
  975. return run(input, {
  976. theme: {
  977. colors: {
  978. blue: { 500: 'hsl(217, 91%, 60%)' },
  979. },
  980. },
  981. }).then((result) => {
  982. expect(result.css).toMatchFormattedCss(output)
  983. expect(result.warnings().length).toBe(0)
  984. })
  985. })
  986. test('Theme function can extract alpha values for colors (7)', () => {
  987. let input = css`
  988. .foo {
  989. color: theme(colors.blue.500 / var(--my-alpha));
  990. }
  991. `
  992. let output = css`
  993. .foo {
  994. color: rgb(var(--foo) / var(--my-alpha));
  995. }
  996. `
  997. return runFull(input, {
  998. theme: {
  999. colors: {
  1000. blue: {
  1001. 500: 'rgb(var(--foo) / <alpha-value>)',
  1002. },
  1003. },
  1004. },
  1005. }).then((result) => {
  1006. expect(result.css).toMatchFormattedCss(output)
  1007. expect(result.warnings().length).toBe(0)
  1008. })
  1009. })
  1010. test('Theme function can extract alpha values for colors (8)', () => {
  1011. let input = css`
  1012. .foo {
  1013. color: theme(colors.blue.500 / theme(opacity.myalpha));
  1014. }
  1015. `
  1016. let output = css`
  1017. .foo {
  1018. color: rgb(var(--foo) / 50%);
  1019. }
  1020. `
  1021. return runFull(input, {
  1022. theme: {
  1023. colors: {
  1024. blue: {
  1025. 500: 'rgb(var(--foo) / <alpha-value>)',
  1026. },
  1027. },
  1028. opacity: {
  1029. myalpha: '50%',
  1030. },
  1031. },
  1032. }).then((result) => {
  1033. expect(result.css).toMatchFormattedCss(output)
  1034. expect(result.warnings().length).toBe(0)
  1035. })
  1036. })
  1037. test('Theme functions replace the alpha value placeholder even with no alpha provided', () => {
  1038. let input = css`
  1039. .foo {
  1040. background: theme(colors.blue.400);
  1041. color: theme(colors.blue.500);
  1042. }
  1043. `
  1044. let output = css`
  1045. .foo {
  1046. color: rgb(var(--foo) / 1);
  1047. background: #00f;
  1048. }
  1049. `
  1050. return runFull(input, {
  1051. theme: {
  1052. colors: {
  1053. blue: {
  1054. 400: 'rgb(0 0 255 / <alpha-value>)',
  1055. 500: 'rgb(var(--foo) / <alpha-value>)',
  1056. },
  1057. },
  1058. },
  1059. }).then((result) => {
  1060. expect(result.css).toMatchFormattedCss(output)
  1061. expect(result.warnings().length).toBe(0)
  1062. })
  1063. })
  1064. test('Theme functions can reference values with slashes in brackets', () => {
  1065. let input = css`
  1066. .foo1 {
  1067. color: theme(colors[a/b]);
  1068. }
  1069. .foo2 {
  1070. color: theme(colors[a/b]/50%);
  1071. }
  1072. `
  1073. let output = css`
  1074. .foo1 {
  1075. color: #000;
  1076. }
  1077. .foo2 {
  1078. color: #00000080;
  1079. }
  1080. `
  1081. return runFull(input, {
  1082. theme: {
  1083. colors: {
  1084. 'a/b': '#000000',
  1085. },
  1086. },
  1087. }).then((result) => {
  1088. expect(result.css).toMatchFormattedCss(output)
  1089. expect(result.warnings().length).toBe(0)
  1090. })
  1091. })
  1092. test('Theme functions with alpha value inside quotes', () => {
  1093. let input = css`
  1094. .foo {
  1095. color: theme('colors.yellow / 50%');
  1096. }
  1097. `
  1098. let output = css`
  1099. .foo {
  1100. color: #f7cc5080;
  1101. }
  1102. `
  1103. return runFull(input, {
  1104. theme: {
  1105. colors: {
  1106. yellow: '#f7cc50',
  1107. },
  1108. },
  1109. }).then((result) => {
  1110. expect(result.css).toMatchFormattedCss(output)
  1111. expect(result.warnings().length).toBe(0)
  1112. })
  1113. })
  1114. test('Theme functions with alpha with quotes value around color only', () => {
  1115. let input = css`
  1116. .foo {
  1117. color: theme('colors.yellow' / 50%);
  1118. }
  1119. `
  1120. let output = css`
  1121. .foo {
  1122. color: #f7cc5080;
  1123. }
  1124. `
  1125. return runFull(input, {
  1126. theme: {
  1127. colors: {
  1128. yellow: '#f7cc50',
  1129. },
  1130. },
  1131. }).then((result) => {
  1132. expect(result.css).toMatchFormattedCss(output)
  1133. expect(result.warnings().length).toBe(0)
  1134. })
  1135. })
  1136. it('can find values with slashes in the theme key while still allowing for alpha values ', () => {
  1137. let input = css`
  1138. .foo00 {
  1139. color: theme(colors.foo-5);
  1140. }
  1141. .foo01 {
  1142. color: theme(colors.foo-5/10);
  1143. }
  1144. .foo02 {
  1145. color: theme(colors.foo-5/10/25);
  1146. }
  1147. .foo03 {
  1148. color: theme(colors.foo-5 / 10);
  1149. }
  1150. .foo04 {
  1151. color: theme(colors.foo-5/10 / 25);
  1152. }
  1153. `
  1154. return runFull(input, {
  1155. theme: {
  1156. colors: {
  1157. 'foo-5': '#050000',
  1158. 'foo-5/10': '#051000',
  1159. 'foo-5/10/25': '#051025',
  1160. },
  1161. },
  1162. }).then((result) => {
  1163. expect(result.css).toMatchFormattedCss(css`
  1164. .foo00 {
  1165. color: #050000;
  1166. }
  1167. .foo01 {
  1168. color: #051000;
  1169. }
  1170. .foo02 {
  1171. color: #051025;
  1172. }
  1173. .foo03 {
  1174. color: #050000;
  1175. }
  1176. .foo04 {
  1177. color: #051000;
  1178. }
  1179. `)
  1180. })
  1181. })
  1182. describe('context dependent', () => {
  1183. let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js')
  1184. let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html')
  1185. let config = {
  1186. content: [filePath],
  1187. corePlugins: { preflight: false },
  1188. }
  1189. // Rebuild the config file for each test
  1190. beforeEach(() => fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`))
  1191. afterEach(() => fs.promises.unlink(configPath))
  1192. test('should not generate when theme fn doesnt resolve', async () => {
  1193. await fs.promises.writeFile(
  1194. filePath,
  1195. html`
  1196. <div class="underline [--box-shadow:theme('boxShadow.doesnotexist')]"></div>
  1197. <div class="bg-[theme('boxShadow.doesnotexist')]"></div>
  1198. `
  1199. )
  1200. // TODO: We need a way to reuse the context in our test suite without requiring writing to files
  1201. // It should be an explicit thing tho — like we create a context and pass it in or something
  1202. let result = await runFull('@tailwind utilities', configPath)
  1203. // 1. On first run it should work because it's been removed from the class cache
  1204. expect(result.css).toMatchFormattedCss(css`
  1205. .underline {
  1206. text-decoration-line: underline;
  1207. }
  1208. `)
  1209. // 2. But we get a warning in the console
  1210. expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
  1211. // 3. The second run should work fine because it's been removed from the class cache
  1212. result = await runFull('@tailwind utilities', configPath)
  1213. expect(result.css).toMatchFormattedCss(css`
  1214. .underline {
  1215. text-decoration-line: underline;
  1216. }
  1217. `)
  1218. // 4. But we've not received any further logs about it
  1219. expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
  1220. })
  1221. test('it works mayhaps', async () => {
  1222. let input = css`
  1223. .test {
  1224. /* prettier-ignore */
  1225. inset: calc(-1 * (2*theme("spacing.4")));
  1226. /* prettier-ignore */
  1227. padding: calc(-1 * (2* theme("spacing.4")));
  1228. }
  1229. `
  1230. let output = css`
  1231. .test {
  1232. /* prettier-ignore */
  1233. inset: calc(-1 * (2*1rem));
  1234. /* prettier-ignore */
  1235. padding: calc(-1 * (2* 1rem));
  1236. }
  1237. `
  1238. return run(input, {
  1239. theme: {
  1240. spacing: {
  1241. 4: '1rem',
  1242. },
  1243. },
  1244. }).then((result) => {
  1245. expect(result.css).toMatchFormattedCss(output)
  1246. expect(result.warnings().length).toBe(0)
  1247. })
  1248. })
  1249. })
  1250. test('it should handle square brackets inside `theme`, inside arbitrary properties', () => {
  1251. let config = {
  1252. content: [
  1253. {
  1254. raw: html` <div class="bg-[--color] sm:[--color:_theme(colors.green[400])]"></div> `,
  1255. },
  1256. ],
  1257. }
  1258. let input = css`
  1259. @tailwind utilities;
  1260. `
  1261. return runFull(input, config).then((result) => {
  1262. expect(result.css).toMatchFormattedCss(css`
  1263. .bg-\[--color\] {
  1264. background-color: var(--color);
  1265. }
  1266. @media (min-width: 640px) {
  1267. .sm\:\[--color\:_theme\(colors\.green\[400\]\)\] {
  1268. --color: #4ade80;
  1269. }
  1270. }
  1271. `)
  1272. })
  1273. })