import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import { describe, expect, test } from 'vitest'
import type { UserConfig } from '../../../../tailwindcss/src/compat/config/types'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
import { migrateArbitraryUtilities } from './migrate-arbitrary-utilities'
import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value'
import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types'
import { migrateOptimizeModifier } from './migrate-optimize-modifier'
const designSystems = new DefaultMap((base: string) => {
return new DefaultMap((input: string) => {
return __unstable__loadDesignSystem(input, { base })
})
})
function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawCandidate: string) {
for (let migration of [
migrateArbitraryUtilities,
migrateDropUnnecessaryDataTypes,
migrateArbitraryValueToBareValue,
migrateOptimizeModifier,
]) {
rawCandidate = migration(designSystem, userConfig, rawCandidate)
}
return rawCandidate
}
describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => {
let testName = '%s => %s (%#)'
if (strategy === 'with-variant') {
testName = testName.replaceAll('%s', 'focus:%s')
} else if (strategy === 'important') {
testName = testName.replaceAll('%s', '%s!')
} else if (strategy === 'prefix') {
testName = testName.replaceAll('%s', 'tw:%s')
}
let input = css`
@import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''};
@theme {
--*: initial;
--spacing: 0.25rem;
--color-red-500: red;
--color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent);
}
`
test.each([
['[text-wrap:balance]', 'text-balance'],
['[display:_flex_]', 'flex'],
['[display:_flex]', 'flex'],
['[display:flex_]', 'flex'],
['leading-[1]', 'leading-none'],
['[color:var(--color-red-500)]', 'text-red-500'],
['[background-color:var(--color-red-500)]', 'bg-red-500'],
['[color:var(--color-red-500)]/25', 'text-red-500/25'],
['[color:var(--color-red-500)]/[25%]', 'text-red-500/25'],
['[color:var(--color-red-500)]/[100%]', 'text-red-500'],
['[color:var(--color-red-500)]/100', 'text-red-500'],
['[color:oklch(62.3%_0.214_259.815)]/50', 'text-primary'],
['[max-height:20px]', 'max-h-[20px]'],
['[grid-column:2]', 'col-2'],
['[grid-column:1234]', 'col-1234'],
['border-[2px]', 'border-2'],
['border-[1234px]', 'border-1234'],
['bg-[position:123px]', 'bg-position-[123px]'],
['bg-[size:123px]', 'bg-size-[123px]'],
['bg-[123px]', 'bg-position-[123px]'],
['w-[64rem]', 'w-256'],
[
'[grid-template-columns:repeat(2,minmax(100px,1fr))]',
'grid-cols-[repeat(2,minmax(100px,1fr))]',
],
['[grid-template-columns:repeat(2,minmax(0,1fr))]', 'grid-cols-2'],
['from-[25%]', 'from-25%'],
['from-[2.5%]', 'from-[2.5%]'],
])(testName, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
result = `focus:${result}`
} else if (strategy === 'important') {
candidate = `${candidate}!`
result = `${result}!`
} else if (strategy === 'prefix') {
candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}`
result = `tw:${result.replaceAll('var(--', 'var(--tw-')}`
}
let designSystem = await designSystems.get(__dirname).get(input)
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
})
const css = String.raw
test('migrate with custom static utility `@utility custom {…}`', async () => {
let candidate = '[--key:value]'
let result = 'custom'
let input = css`
@import 'tailwindcss';
@theme {
--*: initial;
}
@utility custom {
--key: value;
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
test('migrate with custom functional utility `@utility custom-* {…}`', async () => {
let candidate = '[--key:value]'
let result = 'custom-value'
let input = css`
@import 'tailwindcss';
@theme {
--*: initial;
}
@utility custom-* {
--key: --value('value');
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
test('migrate with custom functional utility `@utility custom-* {…}` that supports bare values', async () => {
let candidate = '[tab-size:4]'
let result = 'tab-4'
let input = css`
@import 'tailwindcss';
@theme {
--*: initial;
}
@utility tab-* {
tab-size: --value(integer);
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
test.each([
['[tab-size:0]', 'tab-0'],
['[tab-size:4]', 'tab-4'],
['[tab-size:8]', 'tab-github'],
['tab-[0]', 'tab-0'],
['tab-[4]', 'tab-4'],
['tab-[8]', 'tab-github'],
])(
'migrate custom @utility from arbitrary values to bare values and named values (based on theme)',
async (candidate, expected) => {
let input = css`
@import 'tailwindcss';
@theme {
--*: initial;
--tab-size-github: 8;
}
@utility tab-* {
tab-size: --value(--tab-size, integer, [integer]);
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(expected)
},
)
describe.each([['@theme'], ['@theme inline']])('%s', (theme) => {
test.each([
['[color:CanvasText]', 'text-canvas'],
['text-[CanvasText]', 'text-canvas'],
])('migrate arbitrary value to theme value %s => %s', async (candidate, result) => {
let input = css`
@import 'tailwindcss';
${theme} {
--*: initial;
--color-canvas: CanvasText;
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
test.each([
['max-w-(--breakpoint-md)', 'max-w-(--breakpoint-md)'],
['max-w-(--container-3xl)', 'max-w-3xl'],
])('migrate arbitrary value to theme value %s => %s', async (candidate, result) => {
let input = css`
@import 'tailwindcss';
${theme} {
--*: initial;
--breakpoint-md: 48rem;
--container-3xl: 48rem;
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
})
test('migrate an arbitrary property without spaces, to a theme value with spaces (canonicalization)', async () => {
let candidate = 'font-[foo,bar,baz]'
let expected = 'font-example'
let input = css`
@import 'tailwindcss';
@theme {
--*: initial;
--font-example: foo, bar, baz;
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(expected)
})
describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => {
let testName = '%s => %s (%#)'
if (strategy === 'with-variant') {
testName = testName.replaceAll('%s', 'focus:%s')
} else if (strategy === 'important') {
testName = testName.replaceAll('%s', '%s!')
} else if (strategy === 'prefix') {
testName = testName.replaceAll('%s', 'tw:%s')
}
test.each([
['w-[64rem]', 'w-256', '0.25rem'],
['w-[124px]', 'w-[124px]', '0.25rem'],
['w-[0.123rem]', 'w-[0.123rem]', '0.25rem'],
['w-[123px]', 'w-123', '1px'],
['w-[256px]', 'w-128', '2px'],
])(testName, async (candidate, expected, spacing) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
expected = `focus:${expected}`
} else if (strategy === 'important') {
candidate = `${candidate}!`
expected = `${expected}!`
} else if (strategy === 'prefix') {
candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}`
expected = `tw:${expected.replaceAll('var(--', 'var(--tw-')}`
}
let input = css`
@import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''};
@theme {
--*: initial;
--spacing: ${spacing};
}
`
let designSystem = await __unstable__loadDesignSystem(input, {
base: __dirname,
})
let migrated = migrate(designSystem, {}, candidate)
expect(migrated).toEqual(expected)
})
})