mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
223 lines
6.9 KiB
JavaScript
223 lines
6.9 KiB
JavaScript
import resolveConfig from '../src/public/resolve-config'
|
|
import { createContext } from '../src/lib/setupContextUtils'
|
|
import bigSign from '../src/util/bigSign'
|
|
import { crosscheck } from './util/run'
|
|
|
|
/**
|
|
* 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(' ')
|
|
}
|
|
|
|
crosscheck(() => {
|
|
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 occurence 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)
|
|
}
|
|
})
|