From 96d4ce2516441f0a5884eb9986db56d746e260e1 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Feb 2022 18:06:41 +0100 Subject: [PATCH] Expose `context.sortClassList(classes)` (#7412) * add prettier-plugin-tailwindcss This will use the prettier plugin in our tests as well, yay consistency! * ensure that both `group` and `peer` can't be used in `@apply` This was only configured for `group` * expose `sortClassList` on the context This function will be used by the `prettier-plugin-tailwindcss` plugin, this way the sorting happens within Tailwind CSS itself adn the `prettier-plugin-tailwindcss` plugin doesn't have to use internal / private APIs. The signature looks like this: ```ts function sortClassList(classes: string[]): string[] ``` E.g.: ```js let sortedClasses = context.sortClassList(['p-1', 'm-1', 'container']) ``` * update changelog * add sort test for utilities with the important modifier e.g.: `!p-4` --- CHANGELOG.md | 4 +- integrations/parcel/tests/integration.test.js | 6 +- .../postcss-cli/tests/integration.test.js | 6 +- integrations/rollup/tests/integration.test.js | 6 +- .../tailwindcss-cli/tests/integration.test.js | 6 +- integrations/vite/tests/integration.test.js | 6 +- .../webpack-4/tests/integration.test.js | 6 +- .../webpack-5/tests/integration.test.js | 6 +- package-lock.json | 22 +++++- package.json | 1 + src/lib/expandApplyAtRules.js | 10 +-- src/lib/generateRules.js | 6 +- src/lib/setupContextUtils.js | 42 +++++++++- tests/apply.test.js | 73 +++++++++++++---- tests/arbitrary-values.test.js | 2 +- tests/customConfig.test.js | 34 ++++---- tests/experimental.test.js | 12 +-- tests/kitchen-sink.test.js | 2 +- tests/layer-at-rules.test.js | 2 +- tests/negative-prefix.test.js | 2 +- tests/parallel-variants.test.js | 2 +- tests/plugins/fontSize.test.js | 24 +++--- tests/plugins/gradientColorStops.test.js | 2 +- tests/resolve-defaults-at-rules.test.js | 4 +- tests/sortClassList.test.js | 78 +++++++++++++++++++ 25 files changed, 274 insertions(+), 90 deletions(-) create mode 100644 tests/sortClassList.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cb731f0ad4eb..aa460b7211fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -Nothing yet! +### Added + +- Expose `context.sortClassList(classes)` ([#7412](https://github.com/tailwindlabs/tailwindcss/pull/7412)) ## [3.0.19] - 2022-02-07 diff --git a/integrations/parcel/tests/integration.test.js b/integrations/parcel/tests/integration.test.js index c562221a8548..c90a8a3695be 100644 --- a/integrations/parcel/tests/integration.test.js +++ b/integrations/parcel/tests/integration.test.js @@ -164,7 +164,7 @@ describe.skip('watcher', () => { 'index.html', html` -
+
` ) @@ -190,7 +190,7 @@ describe.skip('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -222,7 +222,7 @@ describe.skip('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/postcss-cli/tests/integration.test.js b/integrations/postcss-cli/tests/integration.test.js index a2035e7ae667..dd736f045d11 100644 --- a/integrations/postcss-cli/tests/integration.test.js +++ b/integrations/postcss-cli/tests/integration.test.js @@ -139,7 +139,7 @@ describe('watcher', () => { }) test('classes are generated when the index.css file changes', async () => { - await writeInputFile('index.html', html`
`) + await writeInputFile('index.html', html`
`) let runningProcess = $('postcss ./src/index.css -o ./dist/main.css -w --verbose') @@ -162,7 +162,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -193,7 +193,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/rollup/tests/integration.test.js b/integrations/rollup/tests/integration.test.js index a4c153181a9d..8b152d27f4fe 100644 --- a/integrations/rollup/tests/integration.test.js +++ b/integrations/rollup/tests/integration.test.js @@ -138,7 +138,7 @@ describe('watcher', () => { }) test(`classes are generated when the index.css file changes`, async () => { - await writeInputFile('index.html', html`
`) + await writeInputFile('index.html', html`
`) let runningProcess = $('rollup -c --watch') await runningProcess.onStderr(ready) @@ -160,7 +160,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -191,7 +191,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/tailwindcss-cli/tests/integration.test.js b/integrations/tailwindcss-cli/tests/integration.test.js index 066417de0635..e3a9aaffb920 100644 --- a/integrations/tailwindcss-cli/tests/integration.test.js +++ b/integrations/tailwindcss-cli/tests/integration.test.js @@ -253,7 +253,7 @@ describe('watcher', () => { }) test('classes are generated when the index.css file changes', async () => { - await writeInputFile('index.html', html`
`) + await writeInputFile('index.html', html`
`) let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w') await runningProcess.onStderr(ready) @@ -275,7 +275,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -306,7 +306,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/vite/tests/integration.test.js b/integrations/vite/tests/integration.test.js index d830a5a9ccc3..6e4cf75a28fc 100644 --- a/integrations/vite/tests/integration.test.js +++ b/integrations/vite/tests/integration.test.js @@ -169,7 +169,7 @@ describe('watcher', () => { 'index.html', html` -
+
` ) @@ -193,7 +193,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -224,7 +224,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/webpack-4/tests/integration.test.js b/integrations/webpack-4/tests/integration.test.js index e42d49f0ec40..cd4e6513e135 100644 --- a/integrations/webpack-4/tests/integration.test.js +++ b/integrations/webpack-4/tests/integration.test.js @@ -140,7 +140,7 @@ describe('watcher', () => { }) test(`classes are generated when the index.css file changes`, async () => { - await writeInputFile('index.html', html`
`) + await writeInputFile('index.html', html`
`) let runningProcess = $('webpack --mode=development --watch') @@ -164,7 +164,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -196,7 +196,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/integrations/webpack-5/tests/integration.test.js b/integrations/webpack-5/tests/integration.test.js index 0135d5f7adeb..ca52eb51f0db 100644 --- a/integrations/webpack-5/tests/integration.test.js +++ b/integrations/webpack-5/tests/integration.test.js @@ -140,7 +140,7 @@ describe('watcher', () => { }) test(`classes are generated when the index.css file changes`, async () => { - await writeInputFile('index.html', html`
`) + await writeInputFile('index.html', html`
`) let runningProcess = $('webpack --mode=development --watch') @@ -164,7 +164,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded; + @apply rounded px-2 py-1; } } ` @@ -196,7 +196,7 @@ describe('watcher', () => { @layer components { .btn { - @apply px-2 py-1 rounded bg-red-500; + @apply rounded bg-red-500 px-2 py-1; } } ` diff --git a/package-lock.json b/package-lock.json index a6b69176906f..32aa59453990 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "postcss-nested": "5.0.6", "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0", + "prettier-plugin-tailwindcss": "^0.1.7", "quick-lru": "^5.1.1", "resolve": "^1.22.0" }, @@ -5588,7 +5589,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "dev": true, "bin": { "prettier": "bin-prettier.js" }, @@ -5608,6 +5608,17 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.7.tgz", + "integrity": "sha512-tmBr45hCLuit2Cz9Pwow0/Jl1bGivYGsfcF29O+3sKcE++ybjz9dfie565S3ZsvAeV8uYer9SRMBWDsHPly2Lg==", + "engines": { + "node": ">=12.17.0" + }, + "peerDependencies": { + "prettier": ">=2.2.0" + } + }, "node_modules/pretty-format": { "version": "27.4.6", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", @@ -10513,8 +10524,7 @@ "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "dev": true + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" }, "prettier-linter-helpers": { "version": "1.0.0", @@ -10525,6 +10535,12 @@ "fast-diff": "^1.1.2" } }, + "prettier-plugin-tailwindcss": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.7.tgz", + "integrity": "sha512-tmBr45hCLuit2Cz9Pwow0/Jl1bGivYGsfcF29O+3sKcE++ybjz9dfie565S3ZsvAeV8uYer9SRMBWDsHPly2Lg==", + "requires": {} + }, "pretty-format": { "version": "27.4.6", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", diff --git a/package.json b/package.json index 7cadd4f2ccdf..21b8da21fe62 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "postcss-nested": "5.0.6", "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0", + "prettier-plugin-tailwindcss": "^0.1.7", "quick-lru": "^5.1.1", "resolve": "^1.22.0" }, diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index d9db3d0123bf..1bc7b8a76f35 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -161,12 +161,12 @@ function processApply(root, context) { } for (let applyCandidate of applyCandidates) { - if (!applyClassCache.has(applyCandidate)) { - if (applyCandidate === prefix(context, 'group')) { - // TODO: Link to specific documentation page with error code. - throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`) - } + if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) { + // TODO: Link to specific documentation page with error code. + throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`) + } + if (!applyClassCache.has(applyCandidate)) { throw apply.error( `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.` ) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 63caa54398c7..c3385403ff30 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -234,7 +234,7 @@ function applyVariant(variant, matches, context) { // For example: // .sm:underline {} is a variant of something in the utilities layer // .sm:container {} is a variant of the container component - clone.nodes[0].raws.tailwind = { parentLayer: meta.layer } + clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer } let withOffset = [ { @@ -387,7 +387,7 @@ function splitWithSeparator(input, separator) { function* recordCandidates(matches, classCandidate) { for (const match of matches) { - match[1].raws.tailwind = { classCandidate } + match[1].raws.tailwind = { ...match[1].raws.tailwind, classCandidate } yield match } @@ -517,6 +517,8 @@ function* resolveMatches(candidate, context) { } for (let match of matches) { + match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate } + // Apply final format selector if (match[0].collectedFormats) { let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 9461b76fa512..beea5b47d34b 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -19,6 +19,12 @@ import { toPath } from '../util/toPath' import log from '../util/log' import negateValue from '../util/negateValue' import isValidArbitraryValue from '../util/isValidArbitraryValue' +import { generateRules } from './generateRules' + +function prefix(context, selector) { + let prefix = context.tailwindConfig.prefix + return typeof prefix === 'function' ? prefix(selector) : prefix + selector +} function parseVariantFormatString(input) { if (input.includes('{')) { @@ -733,9 +739,43 @@ function registerPlugins(plugins, context) { } } + // A list of utilities that are used by certain Tailwind CSS utilities but + // that don't exist on their own. This will result in them "not existing" and + // sorting could be weird since you still require them in order to make the + // host utitlies work properly. (Thanks Biology) + let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')]) + context.sortClassList = function sortClassList(classes) { + let sortedClassNames = new Map() + for (let [sort, rule] of generateRules(new Set(classes), context)) { + if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue + sortedClassNames.set(rule.raws.tailwind.candidate, sort) + } + + return classes + .map((className) => { + let order = sortedClassNames.get(className) ?? null + + if (order === null && parasiteUtilities.has(className)) { + // This will make sure that it is at the very beginning of the + // `components` layer which technically means 'before any + // components'. + order = context.layerOrder.components + } + + return [className, order] + }) + .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) + } + // Generate a list of strings for autocompletion purposes, e.g. // ['uppercase', 'lowercase', ...] - context.getClassList = function () { + context.getClassList = function getClassList() { let output = [] for (let util of classList) { diff --git a/tests/apply.test.js b/tests/apply.test.js index ad1e906819ed..a015262c3128 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -15,20 +15,20 @@ test('@apply', () => { @layer components { .basic-example { - @apply px-4 py-2 bg-blue-500 rounded-md; + @apply rounded-md bg-blue-500 px-4 py-2; } .class-order { - @apply pt-4 pr-1 px-3 py-7 p-8; + @apply p-8 px-3 py-7 pt-4 pr-1; } .with-additional-properties { font-weight: 500; @apply text-right; } .variants { - @apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium font-semibold; + @apply font-semibold hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; } .only-variants { - @apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium; + @apply hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; } .apply-group-variant { @apply group-hover:text-center lg:group-hover:text-left; @@ -41,7 +41,7 @@ test('@apply', () => { } .multiple, .selectors { - @apply px-4 py-2 bg-blue-500 rounded-md; + @apply rounded-md bg-blue-500 px-4 py-2; } .multiple-variants, .selectors-variants { @@ -52,7 +52,7 @@ test('@apply', () => { @apply group-hover:text-center lg:group-hover:text-left; } .complex-utilities { - @apply ordinal tabular-nums focus:diagonal-fractions shadow-lg hover:shadow-xl; + @apply ordinal tabular-nums shadow-lg hover:shadow-xl focus:diagonal-fractions; } .use-base-only-a { @apply font-bold; @@ -67,10 +67,10 @@ test('@apply', () => { @apply use-dependant-only-a font-normal; } .btn { - @apply font-bold py-2 px-4 rounded; + @apply rounded py-2 px-4 font-bold; } .btn-blue { - @apply btn bg-blue-500 hover:bg-blue-700 text-white; + @apply btn bg-blue-500 text-white hover:bg-blue-700; } .recursive-apply-a { @apply font-black sm:font-thin; @@ -96,7 +96,7 @@ test('@apply', () => { } h1 { - @apply text-2xl lg:text-2xl sm:text-3xl; + @apply text-2xl sm:text-3xl lg:text-2xl; } h2 { @apply text-2xl; @@ -105,7 +105,7 @@ test('@apply', () => { } .important-modifier { - @apply px-4 !rounded-md; + @apply !rounded-md px-4; } .important-modifier-variant { @@ -249,9 +249,54 @@ test('@apply error when using a prefixed .group utility', async () => { ) }) +test('@apply error when using .peer utility', async () => { + let config = { + darkMode: 'class', + content: [{ raw: '
' }], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + @layer components { + .foo { + @apply peer; + } + } + ` + + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'peer' utility` + ) +}) + +test('@apply error when using a prefixed .peer utility', async () => { + let config = { + prefix: 'tw-', + darkMode: 'class', + content: [{ raw: html`
` }], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + @layer components { + .foo { + @apply tw-peer; + } + } + ` + + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'tw-peer' utility` + ) +}) + test('@apply classes from outside a @layer', async () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], } let input = css` @@ -317,7 +362,7 @@ test('@apply classes from outside a @layer', async () => { test('@applying classes from outside a @layer respects the source order', async () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], } let input = css` @@ -406,7 +451,7 @@ it('should remove duplicate properties when using apply with similar properties' @tailwind utilities; .foo { - @apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2; + @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform; } ` @@ -778,7 +823,7 @@ it('should be possible to apply user css without tailwind directives', () => { background-color: blue; } .foo { - @apply absolute bar bop; + @apply bar bop absolute; } ` diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index f0cfc8a47ed2..da779bdf0be3 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -20,7 +20,7 @@ it('should be possible to differentiate between decoration utilities', () => { let config = { content: [ { - raw: html`
`, + raw: html`
`, }, ], } diff --git a/tests/customConfig.test.js b/tests/customConfig.test.js index baea15d76884..0b071461b8fb 100644 --- a/tests/customConfig.test.js +++ b/tests/customConfig.test.js @@ -185,7 +185,7 @@ test('tailwind.config.js is picked up by default when passing an empty object', test('the default config can be overridden using the presets key', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], presets: [ { theme: { @@ -200,22 +200,22 @@ test('the default config can be overridden using the presets key', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .min-h-0 { - min-height: 0px; - } .min-h-primary { min-height: 48px; } .min-h-secondary { min-height: 24px; } + .min-h-0 { + min-height: 0px; + } `) }) }) test('presets can be functions', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], presets: [ () => ({ theme: { @@ -230,22 +230,22 @@ test('presets can be functions', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .min-h-0 { - min-height: 0px; - } .min-h-primary { min-height: 48px; } .min-h-secondary { min-height: 24px; } + .min-h-0 { + min-height: 0px; + } `) }) }) test('the default config can be removed by using an empty presets key in a preset', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], presets: [ { presets: [], @@ -273,7 +273,7 @@ test('the default config can be removed by using an empty presets key in a prese test('presets can have their own presets', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], presets: [ { presets: [], @@ -312,6 +312,9 @@ test('presets can have their own presets', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchFormattedCss(css` + .bg-red { + background-color: #ee0000; + } .bg-transparent { background-color: transparent; } @@ -321,16 +324,13 @@ test('presets can have their own presets', () => { .bg-white { background-color: white; } - .bg-red { - background-color: #ee0000; - } `) }) }) test('function presets can be mixed with object presets', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], presets: [ () => ({ presets: [], @@ -369,6 +369,9 @@ test('function presets can be mixed with object presets', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchFormattedCss(css` + .bg-red { + background-color: #ee0000; + } .bg-transparent { background-color: transparent; } @@ -378,9 +381,6 @@ test('function presets can be mixed with object presets', () => { .bg-white { background-color: white; } - .bg-red { - background-color: #ee0000; - } `) }) }) diff --git a/tests/experimental.test.js b/tests/experimental.test.js index 655bb84f8cd0..277136882d10 100644 --- a/tests/experimental.test.js +++ b/tests/experimental.test.js @@ -3,7 +3,7 @@ import { run, html, css } from './util/run' test('experimental universal selector improvements (box-shadow)', () => { let config = { experimental: 'all', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } @@ -39,7 +39,7 @@ test('experimental universal selector improvements (box-shadow)', () => { test('experimental universal selector improvements (pseudo hover)', () => { let config = { experimental: 'all', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } @@ -75,7 +75,7 @@ test('experimental universal selector improvements (pseudo hover)', () => { test('experimental universal selector improvements (multiple classes: group)', () => { let config = { experimental: 'all', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } @@ -111,7 +111,7 @@ test('experimental universal selector improvements (multiple classes: group)', ( test('experimental universal selector improvements (child selectors: divide-y)', () => { let config = { experimental: 'all', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } @@ -138,7 +138,7 @@ test('experimental universal selector improvements (child selectors: divide-y)', test('experimental universal selector improvements (hover:divide-y)', () => { let config = { experimental: 'all', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } @@ -166,7 +166,7 @@ test('experimental universal selector improvements (#app important)', () => { let config = { experimental: 'all', important: '#app', - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } diff --git a/tests/kitchen-sink.test.js b/tests/kitchen-sink.test.js index 83b3e6e73653..1d095680752c 100644 --- a/tests/kitchen-sink.test.js +++ b/tests/kitchen-sink.test.js @@ -143,7 +143,7 @@ test('it works', () => { @apply m-5 mt-6; } .apply-order-b { - @apply mt-6 m-5; + @apply m-5 mt-6; } .apply-dark-group-example-a { @apply dark:group-hover:bg-green-500; diff --git a/tests/layer-at-rules.test.js b/tests/layer-at-rules.test.js index 547c81fa06d4..a0404cbf6dab 100644 --- a/tests/layer-at-rules.test.js +++ b/tests/layer-at-rules.test.js @@ -5,7 +5,7 @@ test('custom user-land utilities', () => { content: [ { raw: html`
`, }, ], diff --git a/tests/negative-prefix.test.js b/tests/negative-prefix.test.js index 92e9e368fa62..0b232d073f60 100644 --- a/tests/negative-prefix.test.js +++ b/tests/negative-prefix.test.js @@ -148,7 +148,7 @@ test('a value that includes a calc', () => { test('a keyword value', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], theme: { margin: { auto: 'auto', diff --git a/tests/parallel-variants.test.js b/tests/parallel-variants.test.js index 5d58ea347c74..94cb1bc13f90 100644 --- a/tests/parallel-variants.test.js +++ b/tests/parallel-variants.test.js @@ -5,7 +5,7 @@ test('basic parallel variants', async () => { content: [ { raw: html`
`, }, ], diff --git a/tests/plugins/fontSize.test.js b/tests/plugins/fontSize.test.js index d772bd4e156c..7304ae3bb839 100644 --- a/tests/plugins/fontSize.test.js +++ b/tests/plugins/fontSize.test.js @@ -2,7 +2,7 @@ import { run, html, css } from '../util/run' test('font-size utilities can include a default line-height', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], theme: { fontSize: { sm: '12px', @@ -14,13 +14,13 @@ test('font-size utilities can include a default line-height', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchCss(css` - .text-sm { - font-size: 12px; - } .text-md { font-size: 16px; line-height: 24px; } + .text-sm { + font-size: 12px; + } .text-lg { font-size: 20px; line-height: 28px; @@ -31,7 +31,7 @@ test('font-size utilities can include a default line-height', () => { test('font-size utilities can include a default letter-spacing', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], theme: { fontSize: { sm: '12px', @@ -43,13 +43,13 @@ test('font-size utilities can include a default letter-spacing', () => { return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchCss(css` - .text-sm { - font-size: 12px; - } .text-md { font-size: 16px; letter-spacing: -0.01em; } + .text-sm { + font-size: 12px; + } .text-lg { font-size: 20px; letter-spacing: -0.02em; @@ -60,7 +60,7 @@ test('font-size utilities can include a default letter-spacing', () => { test('font-size utilities can include a default line-height and letter-spacing', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], theme: { fontSize: { sm: '12px', @@ -72,14 +72,14 @@ test('font-size utilities can include a default line-height and letter-spacing', return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchCss(css` - .text-sm { - font-size: 12px; - } .text-md { font-size: 16px; line-height: 24px; letter-spacing: -0.01em; } + .text-sm { + font-size: 12px; + } .text-lg { font-size: 20px; line-height: 28px; diff --git a/tests/plugins/gradientColorStops.test.js b/tests/plugins/gradientColorStops.test.js index 9dd5b71de802..5ad14feb1808 100644 --- a/tests/plugins/gradientColorStops.test.js +++ b/tests/plugins/gradientColorStops.test.js @@ -5,7 +5,7 @@ test('opacity variables are given to colors defined as closures', () => { content: [ { raw: html`
`, }, ], diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js index 6adf398059d9..249d1c15f025 100644 --- a/tests/resolve-defaults-at-rules.test.js +++ b/tests/resolve-defaults-at-rules.test.js @@ -2,7 +2,7 @@ import { run, html, css } from './util/run' test('basic utilities', async () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } @@ -526,7 +526,7 @@ test('with borders', async () => { test('with shadows', async () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: ['boxShadow', 'ringColor', 'ringWidth'], } diff --git a/tests/sortClassList.test.js b/tests/sortClassList.test.js new file mode 100644 index 000000000000..0220f190727a --- /dev/null +++ b/tests/sortClassList.test.js @@ -0,0 +1,78 @@ +import resolveConfig from '../src/public/resolve-config' +import { createContext } from '../src/lib/setupContextUtils' + +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', 'px-3 !py-4'], + ['!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 hover:underline focus:hover:container', + ], + + // 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(context.sortClassList(input.split(' ')).join(' ')).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-px-3 !tw-py-4'], + ['!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 hover:tw-underline focus:hover:tw-container', + ], + + // 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(context.sortClassList(input.split(' ')).join(' ')).toEqual(output) + } +)