format-variant-selector.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import { finalizeSelector } from '../src/util/formatVariantSelector'
  2. it('should be possible to add a simple variant to a simple selector', () => {
  3. let selector = '.text-center'
  4. let candidate = 'hover:text-center'
  5. let formats = [{ format: '&:hover', respectPrefix: true }]
  6. expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
  7. })
  8. it('should be possible to add a multiple simple variants to a simple selector', () => {
  9. let selector = '.text-center'
  10. let candidate = 'focus:hover:text-center'
  11. let formats = [
  12. { format: '&:hover', respectPrefix: true },
  13. { format: '&:focus', respectPrefix: true },
  14. ]
  15. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  16. '.focus\\:hover\\:text-center:hover:focus'
  17. )
  18. })
  19. it('should be possible to add a simple variant to a selector containing escaped parts', () => {
  20. let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
  21. let candidate = 'hover:bg-[rgba(0,0,0)]'
  22. let formats = [{ format: '&:hover', respectPrefix: true }]
  23. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  24. '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
  25. )
  26. })
  27. it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
  28. let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
  29. let candidate = 'hover:bg-[rgba(0,0,0)]'
  30. let formats = [{ format: '&:hover', respectPrefix: true }]
  31. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  32. '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
  33. )
  34. })
  35. it('should be possible to add a simple variant to a more complex selector', () => {
  36. let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
  37. let candidate = 'hover:space-x-4'
  38. let formats = [{ format: '&:hover', respectPrefix: true }]
  39. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  40. '.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
  41. )
  42. })
  43. it('should be possible to add multiple simple variants to a more complex selector', () => {
  44. let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
  45. let candidate = 'disabled:focus:hover:space-x-4'
  46. let formats = [
  47. { format: '&:hover', respectPrefix: true },
  48. { format: '&:focus', respectPrefix: true },
  49. { format: '&:disabled', respectPrefix: true },
  50. ]
  51. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  52. '.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
  53. )
  54. })
  55. it('should be possible to add a single merge variant to a simple selector', () => {
  56. let selector = '.text-center'
  57. let candidate = 'group-hover:text-center'
  58. let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
  59. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  60. '.group:hover .group-hover\\:text-center'
  61. )
  62. })
  63. it('should be possible to add multiple merge variants to a simple selector', () => {
  64. let selector = '.text-center'
  65. let candidate = 'group-focus:group-hover:text-center'
  66. let formats = [
  67. { format: ':merge(.group):hover &', respectPrefix: true },
  68. { format: ':merge(.group):focus &', respectPrefix: true },
  69. ]
  70. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  71. '.group:focus:hover .group-focus\\:group-hover\\:text-center'
  72. )
  73. })
  74. it('should be possible to add a single merge variant to a more complex selector', () => {
  75. let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
  76. let candidate = 'group-hover:space-x-4'
  77. let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
  78. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  79. '.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
  80. )
  81. })
  82. it('should be possible to add multiple merge variants to a more complex selector', () => {
  83. let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
  84. let candidate = 'group-focus:group-hover:space-x-4'
  85. let formats = [
  86. { format: ':merge(.group):hover &', respectPrefix: true },
  87. { format: ':merge(.group):focus &', respectPrefix: true },
  88. ]
  89. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  90. '.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
  91. )
  92. })
  93. it('should be possible to add multiple unique merge variants to a simple selector', () => {
  94. let selector = '.text-center'
  95. let candidate = 'peer-focus:group-hover:text-center'
  96. let formats = [
  97. { format: ':merge(.group):hover &', respectPrefix: true },
  98. { format: ':merge(.peer):focus ~ &' },
  99. ]
  100. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  101. '.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
  102. )
  103. })
  104. it('should be possible to add multiple unique merge variants to a simple selector', () => {
  105. let selector = '.text-center'
  106. let candidate = 'group-hover:peer-focus:text-center'
  107. let formats = [
  108. { format: ':merge(.peer):focus ~ &', respectPrefix: true },
  109. { format: ':merge(.group):hover &', respectPrefix: true },
  110. ]
  111. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  112. '.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
  113. )
  114. })
  115. it('should be possible to use multiple :merge() calls with different "arguments"', () => {
  116. let selector = '.foo'
  117. let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
  118. let formats = [
  119. { format: ':merge(.group):hover &', respectPrefix: true },
  120. { format: ':merge(.peer):hover ~ &', respectPrefix: true },
  121. { format: ':merge(.group):focus &', respectPrefix: true },
  122. { format: ':merge(.peer):focus ~ &', respectPrefix: true },
  123. ]
  124. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  125. '.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
  126. )
  127. })
  128. it('group hover and prose headings combination', () => {
  129. let selector = '.text-center'
  130. let candidate = 'group-hover:prose-headings:text-center'
  131. let formats = [
  132. { format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
  133. { format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
  134. ]
  135. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  136. '.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
  137. )
  138. })
  139. it('group hover and prose headings combination flipped', () => {
  140. let selector = '.text-center'
  141. let candidate = 'prose-headings:group-hover:text-center'
  142. let formats = [
  143. { format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
  144. { format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
  145. ]
  146. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  147. ':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
  148. )
  149. })
  150. it('should be possible to handle a complex utility', () => {
  151. let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
  152. let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
  153. let formats = [
  154. { format: '&:hover', respectPrefix: true }, // Hover
  155. { format: '&:focus', respectPrefix: true }, // Focus
  156. { format: ':merge(.group):focus &', respectPrefix: true }, // Group focus
  157. { format: ':merge(.group):hover &', respectPrefix: true }, // Group hover
  158. { format: ':merge(.peer):first-child ~ &', respectPrefix: true }, // Peer first-child
  159. { format: ':merge(.peer):disabled ~ &', respectPrefix: true }, // Peer disabled
  160. ]
  161. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  162. '.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
  163. )
  164. })
  165. it('should match base utilities that are prefixed', () => {
  166. let context = { tailwindConfig: { prefix: 'tw-' } }
  167. let selector = '.tw-text-center'
  168. let candidate = 'tw-text-center'
  169. let formats = []
  170. expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
  171. })
  172. it('should prefix classes from variants', () => {
  173. let context = { tailwindConfig: { prefix: 'tw-' } }
  174. let selector = '.tw-text-center'
  175. let candidate = 'foo:tw-text-center'
  176. let formats = [{ format: '.foo &', respectPrefix: true }]
  177. expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
  178. '.tw-foo .foo\\:tw-text-center'
  179. )
  180. })
  181. it('should not prefix classes from arbitrary variants', () => {
  182. let context = { tailwindConfig: { prefix: 'tw-' } }
  183. let selector = '.tw-text-center'
  184. let candidate = '[.foo_&]:tw-text-center'
  185. let formats = [{ format: '.foo &', respectPrefix: false }]
  186. expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
  187. '.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
  188. )
  189. })
  190. it('Merged selectors with mixed combinators uses the first one', () => {
  191. // This isn't explicitly specced behavior but it is how it works today
  192. let selector = '.text-center'
  193. let candidate = 'text-center'
  194. let formats = [
  195. { format: ':merge(.group):focus > &', respectPrefix: false },
  196. { format: ':merge(.group):hover &', respectPrefix: false },
  197. ]
  198. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  199. '.group:hover:focus > .text-center'
  200. )
  201. })
  202. describe('real examples', () => {
  203. it('example a', () => {
  204. let selector = '.placeholder-red-500::placeholder'
  205. let candidate = 'hover:placeholder-red-500'
  206. let formats = [{ format: '&:hover', respectPrefix: true }]
  207. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  208. '.hover\\:placeholder-red-500:hover::placeholder'
  209. )
  210. })
  211. it('example b', () => {
  212. let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
  213. let candidate = 'group-hover:hover:space-x-4'
  214. let formats = [
  215. { format: '&:hover', respectPrefix: true },
  216. { format: ':merge(.group):hover &', respectPrefix: true },
  217. ]
  218. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  219. '.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
  220. )
  221. })
  222. it('should work for group-hover and class dark mode combinations', () => {
  223. let selector = '.text-center'
  224. let candidate = 'dark:group-hover:text-center'
  225. let formats = [
  226. { format: ':merge(.group):hover &', respectPrefix: true },
  227. { format: '.dark &', respectPrefix: true },
  228. ]
  229. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  230. '.dark .group:hover .dark\\:group-hover\\:text-center'
  231. )
  232. })
  233. it('should work for group-hover and class dark mode combinations (reversed)', () => {
  234. let selector = '.text-center'
  235. let candidate = 'group-hover:dark:text-center'
  236. let formats = [{ format: '.dark &' }, { format: ':merge(.group):hover &', respectPrefix: true }]
  237. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  238. '.group:hover .dark .group-hover\\:dark\\:text-center'
  239. )
  240. })
  241. describe('prose-headings', () => {
  242. it('should be possible to use hover:prose-headings:text-center', () => {
  243. let selector = '.text-center'
  244. let candidate = 'hover:prose-headings:text-center'
  245. let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
  246. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  247. ':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
  248. )
  249. })
  250. it('should be possible to use prose-headings:hover:text-center', () => {
  251. let selector = '.text-center'
  252. let candidate = 'prose-headings:hover:text-center'
  253. let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
  254. expect(finalizeSelector(selector, formats, { candidate })).toEqual(
  255. ':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
  256. )
  257. })
  258. })
  259. })
  260. describe('pseudo elements', () => {
  261. it.each`
  262. before | after
  263. ${'&::before'} | ${'&::before'}
  264. ${'&::before:hover'} | ${'&:hover::before'}
  265. ${'&:before:hover'} | ${'&:hover:before'}
  266. ${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
  267. ${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
  268. ${'.parent:hover &'} | ${'.parent:hover &'}
  269. ${'.parent::before &'} | ${'.parent &::before'}
  270. ${'.parent::before &:hover'} | ${'.parent &:hover::before'}
  271. ${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
  272. ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
  273. ${'#app :is(.dark &::before)'} | ${'#app :is(.dark &)::before'}
  274. ${'#app :is(:is(.dark &)::before)'} | ${'#app :is(:is(.dark &))::before'}
  275. ${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
  276. ${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
  277. ${'.parent::marker li'} | ${'.parent li::marker'}
  278. ${'.parent::selection li'} | ${'.parent li::selection'}
  279. ${'.parent::placeholder input'} | ${'.parent input::placeholder'}
  280. ${'.parent::backdrop dialog'} | ${'.parent dialog::backdrop'}
  281. `('should translate "$before" into "$after"', ({ before, after }) => {
  282. let result = finalizeSelector('.a', [{ format: before, respectPrefix: true }], {
  283. candidate: 'a',
  284. })
  285. expect(result).toEqual(after.replace('&', '.a'))
  286. })
  287. })