123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import resolveConfig from '../src/public/resolve-config'
- import { createContext } from '../src/lib/setupContextUtils'
- import bigSign from '../src/util/bigSign'
- /**
- * This is a function that the prettier-plugin-tailwindcss would use. It would
- * do the actual sorting based on the classes and order we return from `getClassOrder`.
- *
- * This way the actual sorting logic is done in the plugin which allows you to
- * put unknown classes at the end for example.
- *
- * @param {Array<[string, bigint]>} arrayOfTuples
- * @returns {string}
- */
- function defaultSort(arrayOfTuples) {
- return arrayOfTuples
- .sort(([, a], [, z]) => {
- if (a === z) return 0
- if (a === null) return -1
- if (z === null) return 1
- return bigSign(a - z)
- })
- .map(([className]) => className)
- .join(' ')
- }
- it('should return a list of tuples with the sort order', () => {
- let input = 'font-bold underline hover:font-medium unknown'
- let config = {}
- let context = createContext(resolveConfig(config))
- expect(context.getClassOrder(input.split(' '))).toEqual([
- ['font-bold', expect.any(BigInt)],
- ['underline', expect.any(BigInt)],
- ['hover:font-medium', expect.any(BigInt)],
- // Unknown values receive `null`
- ['unknown', null],
- ])
- })
- it.each([
- // Utitlies
- ['px-3 p-1 py-3', 'p-1 px-3 py-3'],
- // Utitlies and components
- ['px-4 container', 'container px-4'],
- // Utilities with variants
- ['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
- // Utitlies with important
- ['px-3 !py-4', '!py-4 px-3'],
- ['!py-4 px-3', '!py-4 px-3'],
- // Components with variants
- ['hover:container container', 'container hover:container'],
- // Components and utilities with variants
- [
- 'focus:hover:container hover:underline hover:container p-1',
- 'p-1 hover:container focus:hover:container hover:underline',
- ],
- // Leave user css order alone, and move to the front
- ['b p-1 a', 'b a p-1'],
- ['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
- // Add special treatment for `group` and `peer`
- ['a peer container underline', 'a peer container underline'],
- ])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
- let config = {}
- let context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- })
- it.each([
- // Utitlies
- ['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
- // Utitlies and components
- ['tw-px-4 tw-container', 'tw-container tw-px-4'],
- // Utilities with variants
- [
- 'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
- 'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
- ],
- // Utitlies with important
- ['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
- ['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
- // Components with variants
- ['hover:tw-container tw-container', 'tw-container hover:tw-container'],
- // Components and utilities with variants
- [
- 'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
- 'tw-p-1 hover:tw-container focus:hover:tw-container hover:tw-underline',
- ],
- // Leave user css order alone, and move to the front
- ['b tw-p-1 a', 'b a tw-p-1'],
- ['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
- // Add special treatment for `group` and `peer`
- ['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
- ])(
- 'should sort "%s" with prefixex based on the order we generate them in to "%s"',
- (input, output) => {
- let config = { prefix: 'tw-' }
- let context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- )
- it('sorts classes deterministically across multiple class lists', () => {
- let classes = [
- [
- 'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
- 'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
- ],
- [
- 'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
- 'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
- ],
- ]
- let config = {}
- // Same context, different class lists
- let context = createContext(resolveConfig(config))
- for (const [input, output] of classes) {
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- // Different context, different class lists
- for (const [input, output] of classes) {
- context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- })
- it('sorts based on first occurrence of a candidate / rule', () => {
- let classes = [
- ['foo-1 foo', 'foo foo-1'],
- ['bar', 'bar'],
- ['foo-1 foo', 'foo foo-1'],
- ]
- let config = {
- theme: {},
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.foo': { display: 'block' },
- '.foo-1': { display: 'block' },
- '.bar': { display: 'block' },
- // This rule matches both the candidate `foo` and `bar`
- // But when sorting `foo` — we've already got a
- // position for `foo` so we should use it
- '.bar .foo': { display: 'block' },
- })
- },
- ],
- }
- // Same context, different class lists
- let context = createContext(resolveConfig(config))
- for (const [input, output] of classes) {
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- // Different context, different class lists
- for (const [input, output] of classes) {
- context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- })
- it('Sorting is unchanged when multiple candidates share the same rule / object', () => {
- let classes = [
- ['x y', 'x y'],
- ['a', 'a'],
- ['x y', 'x y'],
- ]
- let config = {
- theme: {},
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.x': { color: 'red' },
- '.a': { color: 'red' },
- // This rule matches both the candidate `a` and `y`
- // When sorting x and y first we would keep that sort order
- // Then sorting `a` we would end up replacing the candidate on the rule
- // Thus causing `y` to no longer have a sort order causing it to be sorted
- // first by accident
- '.y .a': { color: 'red' },
- })
- },
- ],
- }
- // Same context, different class lists
- let context = createContext(resolveConfig(config))
- for (const [input, output] of classes) {
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- // Different context, different class lists
- for (const [input, output] of classes) {
- context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- })
|