[WIP] Unify JIT and AOT code paths (#4188)
* WIP * WIP * Finish combining JIT and AOT plugins Still lots of clean up that can be done in some of the more complex ones, but at least it's one file per plugin now. * Remove unused import * Fix AOT generation bugs * Move corePlugins/index.js to corePlugins.js * Convert JIT files to ESM * Move tests * Reorder core plugins to match JIT order * Update AOT apply tests * Unify utils * Combine plugin lists to one single source of truth * Finish resolving merge conflicts, fix tests Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
54aa5be7ce
commit
bfc61625d9
340 changed files with 428688 additions and 430807 deletions
1405
tests/applyAtRule.test.js
Normal file
1405
tests/applyAtRule.test.js
Normal file
File diff suppressed because it is too large
Load diff
19
tests/cli.compile.test.js
Normal file
19
tests/cli.compile.test.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import path from 'path'
|
||||
|
||||
import autoprefixer from 'autoprefixer'
|
||||
|
||||
import tailwind from '../src'
|
||||
import compile from '../src/cli/compile'
|
||||
|
||||
describe('cli compile', () => {
|
||||
const inputFile = path.resolve(__dirname, 'fixtures/tailwind-input.css')
|
||||
const outputFile = 'output.css'
|
||||
const plugins = [tailwind(), autoprefixer]
|
||||
|
||||
it('compiles CSS file', () => {
|
||||
return compile({ inputFile, outputFile, plugins }).then((result) => {
|
||||
expect(result.css).toContain('.example')
|
||||
expect(result.css).toContain('-webkit-background-clip')
|
||||
})
|
||||
})
|
||||
})
|
||||
122
tests/cli.test.js
Normal file
122
tests/cli.test.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import path from 'path'
|
||||
|
||||
import cli from '../src/cli/main'
|
||||
import * as constants from '../src/constants'
|
||||
import * as utils from '../src/cli/utils'
|
||||
import runInTempDirectory from '../jest/runInTempDirectory'
|
||||
|
||||
describe('cli', () => {
|
||||
const inputCssPath = path.resolve(__dirname, 'fixtures/tailwind-input.css')
|
||||
const customConfigPath = path.resolve(__dirname, 'fixtures/custom-config.js')
|
||||
const esmPackageJsonPath = path.resolve(__dirname, 'fixtures/esm-package.json')
|
||||
const defaultConfigFixture = utils.readFile(constants.defaultConfigStubFile)
|
||||
const simpleConfigFixture = utils.readFile(constants.simpleConfigStubFile)
|
||||
const defaultPostCssConfigFixture = utils.readFile(constants.defaultPostCssConfigStubFile)
|
||||
|
||||
beforeEach(() => {
|
||||
console.log = jest.fn()
|
||||
process.stdout.write = jest.fn()
|
||||
})
|
||||
|
||||
describe('init', () => {
|
||||
it('creates a Tailwind config file', () => {
|
||||
return runInTempDirectory(() => {
|
||||
return cli(['init']).then(() => {
|
||||
expect(utils.readFile(constants.defaultConfigFile)).toEqual(simpleConfigFixture)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a Tailwind config file and a postcss.config.js file', () => {
|
||||
return runInTempDirectory(() => {
|
||||
return cli(['init', '-p']).then(() => {
|
||||
expect(utils.readFile(constants.defaultConfigFile)).toEqual(simpleConfigFixture)
|
||||
expect(utils.readFile(constants.defaultPostCssConfigFile)).toEqual(
|
||||
defaultPostCssConfigFixture
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a full Tailwind config file', () => {
|
||||
return runInTempDirectory(() => {
|
||||
return cli(['init', '--full']).then(() => {
|
||||
expect(utils.readFile(constants.defaultConfigFile)).toEqual(
|
||||
defaultConfigFixture.replace('../colors', 'tailwindcss/colors')
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a .cjs Tailwind config file inside of an ESM project', () => {
|
||||
return runInTempDirectory(() => {
|
||||
utils.writeFile('package.json', utils.readFile(esmPackageJsonPath))
|
||||
return cli(['init']).then(() => {
|
||||
expect(utils.readFile(constants.cjsConfigFile)).toEqual(simpleConfigFixture)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a .cjs Tailwind config file and a postcss.config.cjs file inside of an ESM project', () => {
|
||||
return runInTempDirectory(() => {
|
||||
utils.writeFile('package.json', utils.readFile(esmPackageJsonPath))
|
||||
return cli(['init', '-p']).then(() => {
|
||||
expect(utils.readFile(constants.cjsConfigFile)).toEqual(simpleConfigFixture)
|
||||
expect(utils.readFile(constants.cjsPostCssConfigFile)).toEqual(
|
||||
defaultPostCssConfigFixture
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a Tailwind config file in a custom location', () => {
|
||||
return runInTempDirectory(() => {
|
||||
return cli(['init', 'custom.js']).then(() => {
|
||||
expect(utils.exists('custom.js')).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('build', () => {
|
||||
it('compiles CSS file using an input css file', () => {
|
||||
return cli(['build', inputCssPath]).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.example')
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file without an input css file', () => {
|
||||
return cli(['build']).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('normalize.css') // base
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.container') // components
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.mx-auto') // utilities
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file using custom configuration', () => {
|
||||
return cli(['build', inputCssPath, '--config', customConfigPath]).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('400px')
|
||||
})
|
||||
})
|
||||
|
||||
it('creates compiled CSS file', () => {
|
||||
return runInTempDirectory(() => {
|
||||
return cli(['build', inputCssPath, '--output', 'output.css']).then(() => {
|
||||
expect(utils.readFile('output.css')).toContain('.example')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file with autoprefixer', () => {
|
||||
return cli(['build', inputCssPath]).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('-webkit-background-clip')
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file without autoprefixer', () => {
|
||||
return cli(['build', inputCssPath, '--no-autoprefixer']).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).not.toContain('-webkit-background-clip')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
73
tests/cli.utils.test.js
Normal file
73
tests/cli.utils.test.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import * as utils from '../src/cli/utils'
|
||||
|
||||
describe('cli utils', () => {
|
||||
describe('parseCliParams', () => {
|
||||
it('parses CLI parameters', () => {
|
||||
const result = utils.parseCliParams(['a', 'b', '-c', 'd'])
|
||||
|
||||
expect(result).toEqual(['a', 'b'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseCliOptions', () => {
|
||||
it('parses CLI options', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b', 'c'], { test: ['b'] })
|
||||
|
||||
expect(result).toEqual({ test: ['c'] })
|
||||
})
|
||||
|
||||
it('parses multiple types of options', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b', 'c', '--test', 'd', '-test', 'e'], {
|
||||
test: ['test', 'b'],
|
||||
})
|
||||
|
||||
expect(result).toEqual({ test: ['c', 'd', 'e'] })
|
||||
})
|
||||
|
||||
it('ignores unknown options', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b', 'c'], {})
|
||||
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
it('maps options', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b', 'c', '-d', 'e'], { test: ['b', 'd'] })
|
||||
|
||||
expect(result).toEqual({ test: ['c', 'e'] })
|
||||
})
|
||||
|
||||
it('parses undefined options', () => {
|
||||
const result = utils.parseCliOptions(['a'], { test: ['b'] })
|
||||
|
||||
expect(result).toEqual({ test: undefined })
|
||||
})
|
||||
|
||||
it('parses flags', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b'], { test: ['b'] })
|
||||
|
||||
expect(result).toEqual({ test: [] })
|
||||
})
|
||||
|
||||
it('accepts multiple values per option', () => {
|
||||
const result = utils.parseCliOptions(['a', '-b', 'c', 'd', '-e', 'f', '-g', 'h'], {
|
||||
test: ['b', 'g'],
|
||||
})
|
||||
|
||||
expect(result).toEqual({ test: ['c', 'd', 'h'] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSimplePath', () => {
|
||||
it('strips leading ./', () => {
|
||||
const result = utils.getSimplePath('./test')
|
||||
|
||||
expect(result).toEqual('test')
|
||||
})
|
||||
|
||||
it('returns unchanged path if it does not begin with ./', () => {
|
||||
const result = utils.getSimplePath('../test')
|
||||
|
||||
expect(result).toEqual('../test')
|
||||
})
|
||||
})
|
||||
})
|
||||
30
tests/configurePlugins.test.js
Normal file
30
tests/configurePlugins.test.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import configurePlugins from '../src/util/configurePlugins'
|
||||
|
||||
test('setting a plugin to false removes it', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(
|
||||
{
|
||||
display: false,
|
||||
},
|
||||
plugins
|
||||
)
|
||||
|
||||
expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
|
||||
})
|
||||
|
||||
test('passing only false removes all plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(false, plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual([])
|
||||
})
|
||||
|
||||
test('passing an array whitelists plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(['display'], plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual(['display'])
|
||||
})
|
||||
336
tests/containerPlugin.test.js
Normal file
336
tests/containerPlugin.test.js
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
import _ from 'lodash'
|
||||
import postcss from 'postcss'
|
||||
import processPlugins from '../src/util/processPlugins'
|
||||
import container from '../src/plugins/container'
|
||||
|
||||
function css(nodes) {
|
||||
return postcss.root({ nodes }).toString()
|
||||
}
|
||||
|
||||
function config(overrides) {
|
||||
return _.defaultsDeep(overrides, {
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
lg: '992px',
|
||||
xl: '1200px',
|
||||
},
|
||||
},
|
||||
prefix: '',
|
||||
})
|
||||
}
|
||||
|
||||
test('options are not required', () => {
|
||||
const { components } = processPlugins([container()], config())
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('screens can be passed explicitly', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('screens are ordered ascending by min-width', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['500px', '400px'],
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('screens are deduplicated by min-width', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
'sm-only': { min: '576px', max: '767px' },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('the container can be centered by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('horizontal padding can be included by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('responsive horizontal padding can be included by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: { min: '768px' },
|
||||
lg: { 'min-width': '992px' },
|
||||
xl: { min: '1200px', max: '1600px' },
|
||||
},
|
||||
container: {
|
||||
padding: {
|
||||
DEFAULT: '1rem',
|
||||
sm: '2rem',
|
||||
lg: '4rem',
|
||||
xl: '5rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 576px;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 992px;
|
||||
padding-right: 4rem;
|
||||
padding-left: 4rem
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding-right: 5rem;
|
||||
padding-left: 5rem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setting all options at once', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('container can use variants', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
container: ['responsive', 'hover'],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants responsive, hover {
|
||||
.container {
|
||||
width: 100%
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
562
tests/customConfig.test.js
Normal file
562
tests/customConfig.test.js
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import postcss from 'postcss'
|
||||
import tailwind from '../src/index'
|
||||
import { cjsConfigFile, defaultConfigFile } from '../src/constants'
|
||||
import inTempDirectory from '../jest/runInTempDirectory'
|
||||
|
||||
test('it uses the values from the custom config file', () => {
|
||||
return postcss([tailwind(path.resolve(`${__dirname}/fixtures/custom-config.js`))])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config can be passed as an object', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config path can be passed using `config` property in an object', () => {
|
||||
return postcss([tailwind({ config: path.resolve(`${__dirname}/fixtures/custom-config.js`) })])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config can be passed under the `config` property', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
config: {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.cjs is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
`module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return postcss([tailwind])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.js is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
`module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return postcss([tailwind])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
`module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return postcss([tailwind({})])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.js is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
`module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return postcss([tailwind({})])
|
||||
.process(
|
||||
`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.mobile\\:foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be overridden using the presets key', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
presets: [
|
||||
{
|
||||
theme: {
|
||||
extend: {
|
||||
minHeight: {
|
||||
24: '24px',
|
||||
},
|
||||
},
|
||||
},
|
||||
corePlugins: ['minHeight'],
|
||||
variants: { minHeight: [] },
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { 48: '48px' } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@tailwind utilities
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-24 {
|
||||
min-height: 24px;
|
||||
}
|
||||
.min-h-48 {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('presets can be functions', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
presets: [
|
||||
() => ({
|
||||
theme: {
|
||||
extend: {
|
||||
minHeight: {
|
||||
24: '24px',
|
||||
},
|
||||
},
|
||||
},
|
||||
corePlugins: ['minHeight'],
|
||||
variants: { minHeight: [] },
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { 48: '48px' } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@tailwind utilities
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-24 {
|
||||
min-height: 24px;
|
||||
}
|
||||
.min-h-48 {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be removed by using an empty presets key in a preset', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
extend: {
|
||||
minHeight: {
|
||||
24: '24px',
|
||||
},
|
||||
},
|
||||
},
|
||||
corePlugins: ['minHeight'],
|
||||
variants: { minHeight: [] },
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { 48: '48px' } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@tailwind utilities
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.min-h-24 {
|
||||
min-height: 24px;
|
||||
}
|
||||
.min-h-48 {
|
||||
min-height: 48px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('presets can have their own presets', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
},
|
||||
{
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@tailwind utilities
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('function presets can be mixed with object presets', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
}),
|
||||
{
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(
|
||||
`
|
||||
@tailwind utilities
|
||||
`,
|
||||
{ from: undefined }
|
||||
)
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
358
tests/darkMode.test.js
Normal file
358
tests/darkMode.test.js
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
import postcss from 'postcss'
|
||||
import tailwind from '../src/index'
|
||||
import createPlugin from '../src/util/createPlugin'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss([tailwind(config)]).process(input, { from: undefined })
|
||||
}
|
||||
|
||||
test('user-defined dark mode variants do not stack', () => {
|
||||
const input = `
|
||||
@variants dark, hover {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
.custom-dark .custom-dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
`
|
||||
|
||||
const userPlugin = createPlugin(function ({ addVariant }) {
|
||||
addVariant('dark', function ({ modifySelectors }) {
|
||||
modifySelectors(function ({ className }) {
|
||||
return `.custom-dark .custom-dark\\:${className}`
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return postcss([tailwind({ plugins: [userPlugin] })])
|
||||
.process(input, { from: undefined })
|
||||
.then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants can be generated using the class strategy', () => {
|
||||
const input = `
|
||||
@variants dark {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { darkMode: 'media' }).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants can be generated even when the user has their own plugins array', () => {
|
||||
const input = `
|
||||
@variants dark {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { darkMode: 'media', plugins: [] }).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants can be generated using the class strategy', () => {
|
||||
const input = `
|
||||
@variants dark {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { darkMode: 'class' }).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants can be disabled', () => {
|
||||
const input = `
|
||||
@variants dark {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { dark: false }).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants are disabled by default', () => {
|
||||
const input = `
|
||||
@variants dark {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('dark mode variants stack with other variants', () => {
|
||||
const input = `
|
||||
@variants responsive, dark, hover, focus {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
.hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.sm\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.sm\\:dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.lg\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.lg\\:dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { darkMode: 'media', theme: { screens: { sm: '500px', lg: '800px' } } }).then(
|
||||
(result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('dark mode variants stack with other variants when using the class strategy', () => {
|
||||
const input = `
|
||||
@variants responsive, dark, group-hover, hover, focus {
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
.text-red {
|
||||
color: red;
|
||||
}
|
||||
.group:hover .group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
.dark .dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .group:hover .dark\\:group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.dark .dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.sm\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.group:hover .sm\\:group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.sm\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
.dark .sm\\:dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .group:hover .sm\\:dark\\:group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .sm\\:dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.dark .sm\\:dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.lg\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.group:hover .lg\\:group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.lg\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
.dark .lg\\:dark\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .group:hover .lg\\:dark\\:group-hover\\:text-red {
|
||||
color: red;
|
||||
}
|
||||
.dark .lg\\:dark\\:hover\\:text-red:hover {
|
||||
color: red;
|
||||
}
|
||||
.dark .lg\\:dark\\:focus\\:text-red:focus {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, { darkMode: 'class', theme: { screens: { sm: '500px', lg: '800px' } } }).then(
|
||||
(result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
}
|
||||
)
|
||||
})
|
||||
11
tests/defaultConfig.test.js
Normal file
11
tests/defaultConfig.test.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import config from '../defaultConfig.js'
|
||||
import configStub from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
test('the default config matches the stub', () => {
|
||||
expect(config).toEqual(configStub)
|
||||
})
|
||||
|
||||
test('modifying the default config does not affect the stub', () => {
|
||||
config.theme = {}
|
||||
expect(config).not.toEqual(configStub)
|
||||
})
|
||||
11
tests/defaultTheme.test.js
Normal file
11
tests/defaultTheme.test.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import theme from '../defaultTheme.js'
|
||||
import configStub from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
test('the default theme matches the stub', () => {
|
||||
expect(theme).toEqual(configStub.theme)
|
||||
})
|
||||
|
||||
test('modifying the default theme does not affect the stub', () => {
|
||||
theme.colors = {}
|
||||
expect(theme).not.toEqual(configStub.theme)
|
||||
})
|
||||
5
tests/escapeClassName.test.js
Normal file
5
tests/escapeClassName.test.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import escapeClassName from '../src/util/escapeClassName'
|
||||
|
||||
test('invalid characters are escaped', () => {
|
||||
expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
|
||||
})
|
||||
7
tests/fixtures/custom-config.js
vendored
Normal file
7
tests/fixtures/custom-config.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}
|
||||
3
tests/fixtures/esm-package.json
vendored
Normal file
3
tests/fixtures/esm-package.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
||||
44
tests/fixtures/purge-example.html
vendored
Normal file
44
tests/fixtures/purge-example.html
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<!-- Basic HTML -->
|
||||
<html>
|
||||
<head>
|
||||
<title>My Page</title>
|
||||
</head>
|
||||
<body id="tailwind">
|
||||
<main>
|
||||
<div class="bg-red-500 md:bg-blue-300 w-1/2"></div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Vue dynamic classes -->
|
||||
<span :class="{ block: enabled, 'md:flow-root': !enabled }"></span>
|
||||
|
||||
<!-- JSX with template strings -->
|
||||
<script>
|
||||
function Component() {
|
||||
return <div class={`h-screen`}></div>
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Custom classes with really weird characters -->
|
||||
<div class="min-h-(screen-4) bg-black! font-%#$@ w-(1/2+8)"></div>
|
||||
|
||||
<!-- Pug -->
|
||||
span.inline-grid.grid-cols-3(class="px-1.5")
|
||||
.col-span-2
|
||||
Hello
|
||||
.col-span-1.text-center
|
||||
World!
|
||||
|
||||
<!-- Haml -->
|
||||
.flow-root
|
||||
.text-green-700.bg-green-100
|
||||
.text-left= content
|
||||
%samp.font-mono{:data-foo => "bar"}= output
|
||||
.col-span-4[aria-hidden=true]
|
||||
.tracking-tight#headline
|
||||
|
||||
<!-- JSON -->
|
||||
{
|
||||
"helloThere": "Hello there, <span class=\"whitespace-nowrap\">Mr. Jones</span>"
|
||||
}
|
||||
12
tests/fixtures/tailwind-input-import.css
vendored
Normal file
12
tests/fixtures/tailwind-input-import.css
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@import 'tailwindcss/base';
|
||||
|
||||
@import 'tailwindcss/components';
|
||||
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
@responsive {
|
||||
.example {
|
||||
@apply font-bold;
|
||||
color: theme('colors.red.500');
|
||||
}
|
||||
}
|
||||
11
tests/fixtures/tailwind-input-with-explicit-screen-utilities.css
vendored
Normal file
11
tests/fixtures/tailwind-input-with-explicit-screen-utilities.css
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
@responsive {
|
||||
.example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
|
||||
.john {
|
||||
content: "wick";
|
||||
}
|
||||
12
tests/fixtures/tailwind-input.css
vendored
Normal file
12
tests/fixtures/tailwind-input.css
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@tailwind base;
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
@responsive {
|
||||
.example {
|
||||
@apply font-bold;
|
||||
color: theme('colors.red.500');
|
||||
}
|
||||
}
|
||||
175412
tests/fixtures/tailwind-output-flagged.css
vendored
Normal file
175412
tests/fixtures/tailwind-output-flagged.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
175412
tests/fixtures/tailwind-output-important.css
vendored
Normal file
175412
tests/fixtures/tailwind-output-important.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
160076
tests/fixtures/tailwind-output-no-color-opacity.css
vendored
Normal file
160076
tests/fixtures/tailwind-output-no-color-opacity.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
37
tests/fixtures/tailwind-output-with-explicit-screen-utilities.css
vendored
Normal file
37
tests/fixtures/tailwind-output-with-explicit-screen-utilities.css
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
.example {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.md\:example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
.\32xl\:example {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.john {
|
||||
content: "wick";
|
||||
}
|
||||
175412
tests/fixtures/tailwind-output.css
vendored
Normal file
175412
tests/fixtures/tailwind-output.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
81
tests/flattenColorPalette.test.js
Normal file
81
tests/flattenColorPalette.test.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import flattenColorPalette from '../src/util/flattenColorPalette'
|
||||
|
||||
test('it flattens nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
DEFAULT: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
purple: 'purple',
|
||||
'white-25': 'rgba(255,255,255,.25)',
|
||||
'white-50': 'rgba(255,255,255,.5)',
|
||||
'white-75': 'rgba(255,255,255,.75)',
|
||||
white: '#fff',
|
||||
'red-1': 'rgb(33,0,0)',
|
||||
'red-2': 'rgb(67,0,0)',
|
||||
'red-3': 'rgb(100,0,0)',
|
||||
'green-1': 'rgb(0,33,0)',
|
||||
'green-2': 'rgb(0,67,0)',
|
||||
'green-3': 'rgb(0,100,0)',
|
||||
'blue-1': 'rgb(0,0,33)',
|
||||
'blue-2': 'rgb(0,0,67)',
|
||||
'blue-3': 'rgb(0,0,100)',
|
||||
})
|
||||
})
|
||||
|
||||
test('it flattens deeply nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
primary: 'purple',
|
||||
secondary: {
|
||||
DEFAULT: 'blue',
|
||||
hover: 'cyan',
|
||||
focus: 'red',
|
||||
},
|
||||
button: {
|
||||
primary: {
|
||||
DEFAULT: 'magenta',
|
||||
hover: 'green',
|
||||
focus: {
|
||||
DEFAULT: 'yellow',
|
||||
variant: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
primary: 'purple',
|
||||
secondary: 'blue',
|
||||
'secondary-hover': 'cyan',
|
||||
'secondary-focus': 'red',
|
||||
'button-primary': 'magenta',
|
||||
'button-primary-hover': 'green',
|
||||
'button-primary-focus': 'yellow',
|
||||
'button-primary-focus-variant': 'orange',
|
||||
})
|
||||
})
|
||||
|
||||
test('it handles empty objects', () => {
|
||||
expect(flattenColorPalette({})).toEqual({})
|
||||
})
|
||||
341
tests/jit/apply.test.css
Normal file
341
tests/jit/apply.test.css
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.basic-example {
|
||||
border-radius: 0.375rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.class-order {
|
||||
padding: 2rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
padding-top: 1.75rem;
|
||||
padding-bottom: 1.75rem;
|
||||
padding-top: 1rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.with-additional-properties {
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
}
|
||||
.variants {
|
||||
font-weight: 600;
|
||||
}
|
||||
.variants:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
.variants:focus {
|
||||
font-weight: 500;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.variants {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.variants:focus {
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
.only-variants:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
.only-variants:focus {
|
||||
font-weight: 500;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.only-variants {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.only-variants:focus {
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
.group:hover .apply-group-variant {
|
||||
text-align: center;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.group:hover .apply-group-variant {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.dark .apply-dark-variant {
|
||||
text-align: center;
|
||||
}
|
||||
.dark .apply-dark-variant:hover {
|
||||
text-align: right;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.dark .apply-dark-variant {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.apply-custom-utility {
|
||||
custom: stuff;
|
||||
}
|
||||
.apply-custom-utility:hover {
|
||||
custom: stuff;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.apply-custom-utility {
|
||||
custom: stuff;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.apply-custom-utility:focus {
|
||||
custom: stuff;
|
||||
}
|
||||
}
|
||||
.multiple,
|
||||
.selectors {
|
||||
border-radius: 0.375rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.multiple-variants:hover,
|
||||
.selectors-variants:hover {
|
||||
text-align: center;
|
||||
}
|
||||
.multiple-variants:active,
|
||||
.selectors-variants:active {
|
||||
text-align: right;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.multiple-variants:focus,
|
||||
.selectors-variants:focus {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.group:hover .multiple-group,
|
||||
.group:hover .selectors-group {
|
||||
text-align: center;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.group:hover .multiple-group,
|
||||
.group:hover .selectors-group {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
/* TODO: This works but the generated CSS is unnecessarily verbose. */
|
||||
.complex-utilities {
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
--tw-ordinal: ordinal;
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.complex-utilities:hover {
|
||||
--tw-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.complex-utilities:focus {
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
--tw-numeric-fraction: diagonal-fractions;
|
||||
}
|
||||
.basic-nesting-parent {
|
||||
.basic-nesting-child {
|
||||
font-weight: 700;
|
||||
}
|
||||
.basic-nesting-child:hover {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.use-base-only-a {
|
||||
font-weight: 700;
|
||||
}
|
||||
.use-dependant-only-b {
|
||||
font-weight: 700;
|
||||
font-weight: 400;
|
||||
}
|
||||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.btn-blue {
|
||||
border-radius: 0.25rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
font-weight: 700;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
||||
}
|
||||
.btn-blue:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(29, 78, 216, var(--tw-bg-opacity));
|
||||
}
|
||||
.recursive-apply-a {
|
||||
font-weight: 900;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.recursive-apply-a {
|
||||
font-weight: 100;
|
||||
}
|
||||
}
|
||||
.recursive-apply-b {
|
||||
font-weight: 900;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.recursive-apply-b {
|
||||
font-weight: 100;
|
||||
}
|
||||
}
|
||||
.recursive-apply-b {
|
||||
font-weight: 600;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.recursive-apply-b {
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
.recursive-apply-c {
|
||||
font-weight: 900;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.recursive-apply-c {
|
||||
font-weight: 100;
|
||||
}
|
||||
}
|
||||
.recursive-apply-c {
|
||||
font-weight: 600;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.recursive-apply-c {
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
.recursive-apply-c {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.recursive-apply-c {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
.use-with-other-properties-base {
|
||||
color: green;
|
||||
font-weight: 700;
|
||||
}
|
||||
.use-with-other-properties-component {
|
||||
color: green;
|
||||
font-weight: 700;
|
||||
}
|
||||
.add-sibling-properties {
|
||||
padding: 2rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.add-sibling-properties:hover {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.add-sibling-properties {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.add-sibling-properties:focus {
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
.add-sibling-properties {
|
||||
padding-top: 3px;
|
||||
color: green;
|
||||
font-weight: 700;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
h1 {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.foo {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.bar {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
|
||||
}
|
||||
39
tests/jit/apply.test.html
Normal file
39
tests/jit/apply.test.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="basic-example"></div>
|
||||
<div class="class-order"></div>
|
||||
<div class="with-additional-properties"></div>
|
||||
<div class="variants"></div>
|
||||
<div class="only-variants"></div>
|
||||
<div class="apply-group-variant"></div>
|
||||
<div class="apply-dark-variant"></div>
|
||||
<div class="apply-custom-utility"></div>
|
||||
<div class="multiple selectors"></div>
|
||||
<div class="multiple-variants selectors-variants"></div>
|
||||
<div class="multiple-group selectors-group"></div>
|
||||
<div class="complex-utilities"></div>
|
||||
<div class="basic-nesting-parent">
|
||||
<div class="basic-nesting-child"></div>
|
||||
</div>
|
||||
<div class="use-base-only-a"></div>
|
||||
<div class="use-dependant-only-b"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn-blue"></div>
|
||||
<div class="recursive-apply-a"></div>
|
||||
<div class="recursive-apply-b"></div>
|
||||
<div class="recursive-apply-c"></div>
|
||||
<div class="use-with-other-properties-base use-with-other-properties-component"></div>
|
||||
<div class="add-sibling-properties"></div>
|
||||
<div class="a b"></div>
|
||||
<div class="foo"></div>
|
||||
<div class="bar"></div>
|
||||
</body>
|
||||
</html>
|
||||
219
tests/jit/apply.test.js
Normal file
219
tests/jit/apply.test.js
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
const { currentTestName } = expect.getState()
|
||||
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: `${path.resolve(__filename)}?test=${currentTestName}`,
|
||||
})
|
||||
}
|
||||
|
||||
test('@apply', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './apply.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.basic-example {
|
||||
@apply px-4 py-2 bg-blue-500 rounded-md;
|
||||
}
|
||||
.class-order {
|
||||
@apply pt-4 pr-1 px-3 py-7 p-8;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.only-variants {
|
||||
@apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium;
|
||||
}
|
||||
.apply-group-variant {
|
||||
@apply group-hover:text-center lg:group-hover:text-left;
|
||||
}
|
||||
.apply-dark-variant {
|
||||
@apply dark:text-center dark:hover:text-right lg:dark:text-left;
|
||||
}
|
||||
.apply-custom-utility {
|
||||
@apply custom-util hover:custom-util lg:custom-util xl:focus:custom-util;
|
||||
}
|
||||
.multiple, .selectors {
|
||||
@apply px-4 py-2 bg-blue-500 rounded-md;
|
||||
}
|
||||
.multiple-variants, .selectors-variants {
|
||||
@apply hover:text-center active:text-right lg:focus:text-left;
|
||||
}
|
||||
.multiple-group, .selectors-group {
|
||||
@apply group-hover:text-center lg:group-hover:text-left;
|
||||
}
|
||||
/* TODO: This works but the generated CSS is unnecessarily verbose. */
|
||||
.complex-utilities {
|
||||
@apply ordinal tabular-nums focus:diagonal-fractions shadow-lg hover:shadow-xl;
|
||||
}
|
||||
.basic-nesting-parent {
|
||||
.basic-nesting-child {
|
||||
@apply font-bold hover:font-normal;
|
||||
}
|
||||
}
|
||||
.use-base-only-a {
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-base-only-b {
|
||||
@apply use-base-only-a font-normal;
|
||||
}
|
||||
.use-dependant-only-a {
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-dependant-only-b {
|
||||
@apply use-dependant-only-a font-normal;
|
||||
}
|
||||
.btn {
|
||||
@apply font-bold py-2 px-4 rounded;
|
||||
}
|
||||
.btn-blue {
|
||||
@apply btn bg-blue-500 hover:bg-blue-700 text-white;
|
||||
}
|
||||
.recursive-apply-a {
|
||||
@apply font-black sm:font-thin;
|
||||
}
|
||||
.recursive-apply-b {
|
||||
@apply recursive-apply-a font-semibold md:font-extralight;
|
||||
}
|
||||
.recursive-apply-c {
|
||||
@apply recursive-apply-b font-bold lg:font-light;
|
||||
}
|
||||
.use-with-other-properties-base {
|
||||
color: green;
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-with-other-properties-component {
|
||||
@apply use-with-other-properties-base;
|
||||
}
|
||||
.add-sibling-properties {
|
||||
padding: 2rem;
|
||||
@apply px-4 hover:px-2 lg:px-10 xl:focus:px-1;
|
||||
padding-top: 3px;
|
||||
@apply use-with-other-properties-base;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-2xl lg:text-2xl sm:text-3xl;
|
||||
}
|
||||
h2 {
|
||||
@apply text-2xl;
|
||||
@apply lg:text-2xl;
|
||||
@apply sm:text-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.custom-util {
|
||||
custom: stuff;
|
||||
}
|
||||
|
||||
.foo {
|
||||
@apply animate-spin;
|
||||
}
|
||||
|
||||
.bar {
|
||||
@apply animate-pulse !important;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './apply.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('@apply error with unknown utility', async () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
purge: [path.resolve(__dirname, './apply.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.foo {
|
||||
@apply a-utility-that-does-not-exist;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError('class does not exist')
|
||||
})
|
||||
|
||||
test('@apply error with nested @screen', async () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
purge: [path.resolve(__dirname, './apply.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.foo {
|
||||
@screen md {
|
||||
@apply text-black;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError(
|
||||
'@apply is not supported within nested at-rules like @screen'
|
||||
)
|
||||
})
|
||||
|
||||
test('@apply error with nested @anyatrulehere', async () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
purge: [path.resolve(__dirname, './apply.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.foo {
|
||||
@genie {
|
||||
@apply text-black;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError(
|
||||
'@apply is not supported within nested at-rules like @genie'
|
||||
)
|
||||
})
|
||||
369
tests/jit/arbitrary-values.test.css
Normal file
369
tests/jit/arbitrary-values.test.css
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.inset-\[11px\] {
|
||||
top: 11px;
|
||||
right: 11px;
|
||||
bottom: 11px;
|
||||
left: 11px;
|
||||
}
|
||||
.col-\[7\] {
|
||||
grid-column: 7;
|
||||
}
|
||||
.col-start-\[7\] {
|
||||
grid-column-start: 7;
|
||||
}
|
||||
.col-end-\[7\] {
|
||||
grid-column-end: 7;
|
||||
}
|
||||
.row-\[7\] {
|
||||
grid-row: 7;
|
||||
}
|
||||
.row-start-\[7\] {
|
||||
grid-row-start: 7;
|
||||
}
|
||||
.row-end-\[7\] {
|
||||
grid-row-end: 7;
|
||||
}
|
||||
.m-\[7px\] {
|
||||
margin: 7px;
|
||||
}
|
||||
.mx-\[7px\] {
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
.my-\[7px\] {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.mt-\[7px\] {
|
||||
margin-top: 7px;
|
||||
}
|
||||
.mr-\[7px\] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.mb-\[7px\] {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.ml-\[7px\] {
|
||||
margin-left: 7px;
|
||||
}
|
||||
.mt-\[clamp\(30px\2c 100px\)\] {
|
||||
margin-top: clamp(30px, 100px);
|
||||
}
|
||||
.h-\[3\.23rem\] {
|
||||
height: 3.23rem;
|
||||
}
|
||||
.h-\[calc\(100\%\+1rem\)\] {
|
||||
height: calc(100% + 1rem);
|
||||
}
|
||||
.h-\[var\(--height\)\] {
|
||||
height: var(--height);
|
||||
}
|
||||
.max-h-\[3\.23rem\] {
|
||||
max-height: 3.23rem;
|
||||
}
|
||||
.max-h-\[calc\(100\%\+1rem\)\] {
|
||||
max-height: calc(100% + 1rem);
|
||||
}
|
||||
.max-h-\[var\(--height\)\] {
|
||||
max-height: var(--height);
|
||||
}
|
||||
.min-h-\[3\.23rem\] {
|
||||
min-height: 3.23rem;
|
||||
}
|
||||
.min-h-\[calc\(100\%\+1rem\)\] {
|
||||
min-height: calc(100% + 1rem);
|
||||
}
|
||||
.min-h-\[var\(--height\)\] {
|
||||
min-height: var(--height);
|
||||
}
|
||||
.w-\[3\.23rem\] {
|
||||
width: 3.23rem;
|
||||
}
|
||||
.w-\[calc\(100\%\+1rem\)\] {
|
||||
width: calc(100% + 1rem);
|
||||
}
|
||||
.w-\[calc\(var\(--10-10px\2c calc\(-20px-\(-30px--40px\)\)\)-50px\)\] {
|
||||
width: calc(var(--10-10px, calc(-20px - (-30px - -40px))) - 50px);
|
||||
}
|
||||
.w-\[var\(--width\)\] {
|
||||
width: var(--width);
|
||||
}
|
||||
.w-\[var\(--width\2c calc\(100\%\+1rem\)\)\] {
|
||||
width: var(--width, calc(100% + 1rem));
|
||||
}
|
||||
.w-\[calc\(100\%\/3-1rem\*2\)\] {
|
||||
width: calc(100% / 3 - 1rem * 2);
|
||||
}
|
||||
.min-w-\[3\.23rem\] {
|
||||
min-width: 3.23rem;
|
||||
}
|
||||
.min-w-\[calc\(100\%\+1rem\)\] {
|
||||
min-width: calc(100% + 1rem);
|
||||
}
|
||||
.min-w-\[var\(--width\)\] {
|
||||
min-width: var(--width);
|
||||
}
|
||||
.max-w-\[3\.23rem\] {
|
||||
max-width: 3.23rem;
|
||||
}
|
||||
.max-w-\[calc\(100\%\+1rem\)\] {
|
||||
max-width: calc(100% + 1rem);
|
||||
}
|
||||
.max-w-\[var\(--width\)\] {
|
||||
max-width: var(--width);
|
||||
}
|
||||
.rotate-\[23deg\] {
|
||||
--tw-rotate: 23deg;
|
||||
}
|
||||
.rotate-\[2\.3rad\] {
|
||||
--tw-rotate: 2.3rad;
|
||||
}
|
||||
.rotate-\[401grad\] {
|
||||
--tw-rotate: 401grad;
|
||||
}
|
||||
.rotate-\[1\.5turn\] {
|
||||
--tw-rotate: 1.5turn;
|
||||
}
|
||||
.skew-x-\[3px\] {
|
||||
--tw-skew-x: 3px;
|
||||
}
|
||||
.skew-y-\[3px\] {
|
||||
--tw-skew-y: 3px;
|
||||
}
|
||||
.grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
|
||||
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
|
||||
}
|
||||
.grid-rows-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
|
||||
grid-template-rows: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
|
||||
}
|
||||
.space-x-\[20cm\] > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(20cm * var(--tw-space-x-reverse));
|
||||
margin-left: calc(20cm * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
.space-x-\[calc\(20\%-1cm\)\] > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(calc(20% - 1cm) * var(--tw-space-x-reverse));
|
||||
margin-left: calc(calc(20% - 1cm) * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
.rounded-\[11px\] {
|
||||
border-radius: 11px;
|
||||
}
|
||||
.rounded-t-\[var\(--radius\)\] {
|
||||
border-top-left-radius: var(--radius);
|
||||
border-top-right-radius: var(--radius);
|
||||
}
|
||||
.rounded-r-\[var\(--radius\)\] {
|
||||
border-top-right-radius: var(--radius);
|
||||
border-bottom-right-radius: var(--radius);
|
||||
}
|
||||
.rounded-b-\[var\(--radius\)\] {
|
||||
border-bottom-right-radius: var(--radius);
|
||||
border-bottom-left-radius: var(--radius);
|
||||
}
|
||||
.rounded-l-\[var\(--radius\)\] {
|
||||
border-top-left-radius: var(--radius);
|
||||
border-bottom-left-radius: var(--radius);
|
||||
}
|
||||
.rounded-tr-\[var\(--radius\)\] {
|
||||
border-top-right-radius: var(--radius);
|
||||
}
|
||||
.rounded-br-\[var\(--radius\)\] {
|
||||
border-bottom-right-radius: var(--radius);
|
||||
}
|
||||
.rounded-bl-\[var\(--radius\)\] {
|
||||
border-bottom-left-radius: var(--radius);
|
||||
}
|
||||
.rounded-tl-\[var\(--radius\)\] {
|
||||
border-top-left-radius: var(--radius);
|
||||
}
|
||||
.border-\[2\.5px\] {
|
||||
border-width: 2.5px;
|
||||
}
|
||||
.border-\[\#f00\] {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(255, 0, 0, var(--tw-border-opacity));
|
||||
}
|
||||
.bg-\[\#0f0\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(0, 255, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[\#ff0000\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(255, 0, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[\#0000ffcc\] {
|
||||
background-color: #0000ffcc;
|
||||
}
|
||||
.bg-\[rgb\(123\2c 123\2c 123\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(123, 123, 123, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[rgba\(123\2c 123\2c 123\2c 0\.5\)\] {
|
||||
background-color: rgba(123, 123, 123, 0.5);
|
||||
}
|
||||
.bg-\[hsl\(0\2c 100\%\2c 50\%\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsla(0, 100%, 50%, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsla\(0\2c 100\%\2c 50\%\2c 0\.3\)\] {
|
||||
background-color: hsla(0, 100%, 50%, 0.3);
|
||||
}
|
||||
.bg-opacity-\[0\.11\] {
|
||||
--tw-bg-opacity: 0.11;
|
||||
}
|
||||
.bg-opacity-\[var\(--value\)\] {
|
||||
--tw-bg-opacity: var(--value);
|
||||
}
|
||||
.from-\[\#da5b66\] {
|
||||
--tw-gradient-from: #da5b66;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(218, 91, 102, 0));
|
||||
}
|
||||
.from-\[var\(--color\)\] {
|
||||
--tw-gradient-from: var(--color);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(255, 255, 255, 0));
|
||||
}
|
||||
.via-\[\#da5b66\] {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), #da5b66,
|
||||
var(--tw-gradient-to, rgba(218, 91, 102, 0));
|
||||
}
|
||||
.via-\[var\(--color\)\] {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--color),
|
||||
var(--tw-gradient-to, rgba(255, 255, 255, 0));
|
||||
}
|
||||
.to-\[\#da5b66\] {
|
||||
--tw-gradient-to: #da5b66;
|
||||
}
|
||||
.to-\[var\(--color\)\] {
|
||||
--tw-gradient-to: var(--color);
|
||||
}
|
||||
.fill-\[\#da5b66\] {
|
||||
fill: #da5b66;
|
||||
}
|
||||
.fill-\[var\(--color\)\] {
|
||||
fill: var(--color);
|
||||
}
|
||||
.stroke-\[\#da5b66\] {
|
||||
stroke: #da5b66;
|
||||
}
|
||||
.p-\[var\(--app-padding\)\] {
|
||||
padding: var(--app-padding);
|
||||
}
|
||||
.text-\[2\.23rem\] {
|
||||
font-size: 2.23rem;
|
||||
}
|
||||
.leading-\[var\(--leading\)\] {
|
||||
line-height: var(--leading);
|
||||
}
|
||||
.tracking-\[var\(--tracking\)\] {
|
||||
letter-spacing: var(--tracking);
|
||||
}
|
||||
.placeholder-\[var\(--placeholder\)\]::placeholder {
|
||||
color: var(--placeholder);
|
||||
}
|
||||
.placeholder-opacity-\[var\(--placeholder-opacity\)\]::placeholder {
|
||||
--tw-placeholder-opacity: var(--placeholder-opacity);
|
||||
}
|
||||
.opacity-\[var\(--opacity\)\] {
|
||||
opacity: var(--opacity);
|
||||
}
|
||||
.outline-\[var\(--outline\)\] {
|
||||
outline: var(--outline);
|
||||
outline-offset: 0;
|
||||
}
|
||||
.ring-\[10px\] {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
|
||||
var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(10px + var(--tw-ring-offset-width))
|
||||
var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
.ring-\[\#76ad65\] {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgba(118, 173, 101, var(--tw-ring-opacity));
|
||||
}
|
||||
.ring-opacity-\[var\(--ring-opacity\)\] {
|
||||
--tw-ring-opacity: var(--ring-opacity);
|
||||
}
|
||||
.ring-offset-\[19rem\] {
|
||||
--tw-ring-offset-width: 19rem;
|
||||
}
|
||||
.ring-offset-\[\#76ad65\] {
|
||||
--tw-ring-offset-color: #76ad65;
|
||||
}
|
||||
.ring-offset-\[\#ad672f\] {
|
||||
--tw-ring-offset-color: #ad672f;
|
||||
}
|
||||
.blur-\[15px\] {
|
||||
--tw-blur: blur(15px);
|
||||
}
|
||||
.brightness-\[300\%\] {
|
||||
--tw-brightness: brightness(300%);
|
||||
}
|
||||
.contrast-\[2\.4\] {
|
||||
--tw-contrast: contrast(2.4);
|
||||
}
|
||||
.grayscale-\[0\.55\] {
|
||||
--tw-grayscale: grayscale(0.55);
|
||||
}
|
||||
.hue-rotate-\[0\.8turn\] {
|
||||
--tw-hue-rotate: hue-rotate(0.8turn);
|
||||
}
|
||||
.invert-\[0\.75\] {
|
||||
--tw-invert: invert(0.75);
|
||||
}
|
||||
.saturate-\[180\%\] {
|
||||
--tw-saturate: saturate(180%);
|
||||
}
|
||||
.sepia-\[0\.2\] {
|
||||
--tw-sepia: sepia(0.2);
|
||||
}
|
||||
.backdrop-blur-\[11px\] {
|
||||
--tw-backdrop-blur: blur(11px);
|
||||
}
|
||||
.backdrop-brightness-\[1\.23\] {
|
||||
--tw-backdrop-brightness: brightness(1.23);
|
||||
}
|
||||
.backdrop-contrast-\[0\.87\] {
|
||||
--tw-backdrop-contrast: contrast(0.87);
|
||||
}
|
||||
.backdrop-grayscale-\[0\.42\] {
|
||||
--tw-backdrop-grayscale: grayscale(0.42);
|
||||
}
|
||||
.backdrop-hue-rotate-\[1\.57rad\] {
|
||||
--tw-backdrop-hue-rotate: hue-rotate(1.57rad);
|
||||
}
|
||||
.backdrop-invert-\[0\.66\] {
|
||||
--tw-backdrop-invert: invert(0.66);
|
||||
}
|
||||
.backdrop-opacity-\[22\%\] {
|
||||
--tw-backdrop-opacity: opacity(22%);
|
||||
}
|
||||
.backdrop-saturate-\[144\%\] {
|
||||
--tw-backdrop-saturate: saturate(144%);
|
||||
}
|
||||
.backdrop-sepia-\[0\.38\] {
|
||||
--tw-backdrop-sepia: sepia(0.38);
|
||||
}
|
||||
.delay-\[var\(--delay\)\] {
|
||||
transition-delay: var(--delay);
|
||||
}
|
||||
.duration-\[2s\] {
|
||||
transition-duration: 2s;
|
||||
}
|
||||
.duration-\[var\(--app-duration\)\] {
|
||||
transition-duration: var(--app-duration);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
|
||||
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
|
||||
}
|
||||
}
|
||||
109
tests/jit/arbitrary-values.test.html
Normal file
109
tests/jit/arbitrary-values.test.html
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-[#0f0] bg-[#ff0000] bg-[#0000ffcc]"></div>
|
||||
<div class="bg-[rgb(123,123,123)] bg-[rgba(123,123,123,0.5)]"></div>
|
||||
<div class="bg-[hsl(0,100%,50%)] bg-[hsla(0,100%,50%,0.3)]"></div>
|
||||
<div class="bg-opacity-[0.11]"></div>
|
||||
<div class="bg-opacity-[var(--value)]"></div>
|
||||
<div class="border-[#f00]"></div>
|
||||
<div class="border-[2.5px]"></div>
|
||||
<div class="w-[3.23rem]"></div>
|
||||
<div class="w-[calc(100%+1rem)]"></div>
|
||||
<div class="w-[calc(var(--10-10px,calc(-20px-(-30px--40px)))-50px)]"></div>
|
||||
<div class="w-[var(--width)]"></div>
|
||||
<div class="w-[var(--width,calc(100%+1rem))]"></div>
|
||||
<div class="w-[calc(100%/3-1rem*2)]"></div>
|
||||
<div class="min-w-[3.23rem]"></div>
|
||||
<div class="min-w-[calc(100%+1rem)]"></div>
|
||||
<div class="min-w-[var(--width)]"></div>
|
||||
<div class="max-w-[3.23rem]"></div>
|
||||
<div class="max-w-[calc(100%+1rem)]"></div>
|
||||
<div class="max-w-[var(--width)]"></div>
|
||||
<div class="h-[3.23rem]"></div>
|
||||
<div class="h-[calc(100%+1rem)]"></div>
|
||||
<div class="h-[var(--height)]"></div>
|
||||
<div class="min-h-[3.23rem]"></div>
|
||||
<div class="min-h-[calc(100%+1rem)]"></div>
|
||||
<div class="min-h-[var(--height)]"></div>
|
||||
<div class="max-h-[3.23rem]"></div>
|
||||
<div class="max-h-[calc(100%+1rem)]"></div>
|
||||
<div class="max-h-[var(--height)]"></div>
|
||||
<div class="space-x-[20cm]"></div>
|
||||
<div class="space-x-[calc(20%-1cm)]"></div>
|
||||
<div class="grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
|
||||
<div class="lg:grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
|
||||
<div class="grid-rows-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
|
||||
<div class="col-[7]"></div>
|
||||
<div class="col-end-[7]"></div>
|
||||
<div class="col-start-[7]"></div>
|
||||
<div class="row-[7]"></div>
|
||||
<div class="row-end-[7]"></div>
|
||||
<div class="row-start-[7]"></div>
|
||||
<div class="rotate-[23deg] rotate-[2.3rad] rotate-[401grad] rotate-[1.5turn]"></div>
|
||||
<div class="skew-x-[3px]"></div>
|
||||
<div class="skew-y-[3px]"></div>
|
||||
<div class="text-[2.23rem]"></div>
|
||||
<div class="duration-[2s]"></div>
|
||||
<div class="m-[7px]"></div>
|
||||
<div class="mx-[7px]"></div>
|
||||
<div class="my-[7px]"></div>
|
||||
<div class="mt-[7px]"></div>
|
||||
<div class="mr-[7px]"></div>
|
||||
<div class="mb-[7px]"></div>
|
||||
<div class="ml-[7px]"></div>
|
||||
<div class="mt-[clamp(30px,100px)]"></div>
|
||||
<div class="rounded-[11px]"></div>
|
||||
<div
|
||||
class="rounded-t-[var(--radius)] rounded-r-[var(--radius)] rounded-b-[var(--radius)] rounded-l-[var(--radius)]"
|
||||
></div>
|
||||
<div
|
||||
class="rounded-tr-[var(--radius)] rounded-br-[var(--radius)] rounded-bl-[var(--radius)] rounded-tl-[var(--radius)]"
|
||||
></div>
|
||||
<div class="duration-[var(--app-duration)]"></div>
|
||||
<div class="p-[var(--app-padding)]"></div>
|
||||
<div class="inset-[11px]"></div>
|
||||
<div class="blur-[15px]"></div>
|
||||
<div class="brightness-[300%]"></div>
|
||||
<div class="contrast-[2.4]"></div>
|
||||
<div class="grayscale-[0.55]"></div>
|
||||
<div class="hue-rotate-[0.8turn]"></div>
|
||||
<div class="invert-[0.75]"></div>
|
||||
<div class="saturate-[180%]"></div>
|
||||
<div class="sepia-[0.2]"></div>
|
||||
<div class="backdrop-blur-[11px]"></div>
|
||||
<div class="backdrop-brightness-[1.23]"></div>
|
||||
<div class="backdrop-contrast-[0.87]"></div>
|
||||
<div class="backdrop-grayscale-[0.42]"></div>
|
||||
<div class="backdrop-hue-rotate-[1.57rad]"></div>
|
||||
<div class="backdrop-invert-[0.66]"></div>
|
||||
<div class="backdrop-opacity-[22%]"></div>
|
||||
<div class="backdrop-saturate-[144%]"></div>
|
||||
<div class="backdrop-sepia-[0.38]"></div>
|
||||
<div class="from-[#da5b66] via-[#da5b66] to-[#da5b66]"></div>
|
||||
<div class="from-[var(--color)] via-[var(--color)] to-[var(--color)]"></div>
|
||||
<div class="fill-[#da5b66]"></div>
|
||||
<div class="fill-[var(--color)]"></div>
|
||||
<div class="stroke-[#da5b66]"></div>
|
||||
<div class="leading-[var(--leading)]"></div>
|
||||
<div class="tracking-[var(--tracking)]"></div>
|
||||
<div class="placeholder-[var(--placeholder)]"></div>
|
||||
<div class="placeholder-opacity-[var(--placeholder-opacity)]"></div>
|
||||
<div class="opacity-[var(--opacity)]"></div>
|
||||
<div class="outline-[var(--outline)]"></div>
|
||||
<div class="ring-[#76ad65]"></div>
|
||||
<div class="ring-offset-[#76ad65]"></div>
|
||||
<div class="ring-[10px]"></div>
|
||||
<div class="ring-offset-[#ad672f]"></div>
|
||||
<div class="ring-offset-[19rem]"></div>
|
||||
<div class="ring-opacity-[var(--ring-opacity)]"></div>
|
||||
<div class="delay-[var(--delay)]"></div>
|
||||
</body>
|
||||
</html>
|
||||
31
tests/jit/arbitrary-values.test.js
Normal file
31
tests/jit/arbitrary-values.test.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, { from: path.resolve(__filename) })
|
||||
}
|
||||
|
||||
test('arbitrary values', () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './arbitrary-values.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './arbitrary-values.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
757
tests/jit/basic-usage.test.css
Normal file
757
tests/jit/basic-usage.test.css
Normal file
|
|
@ -0,0 +1,757 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.inset-0 {
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.inset-y-4 {
|
||||
top: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
.inset-x-2 {
|
||||
left: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
.top-6 {
|
||||
top: 1.5rem;
|
||||
}
|
||||
.right-8 {
|
||||
right: 2rem;
|
||||
}
|
||||
.bottom-12 {
|
||||
bottom: 3rem;
|
||||
}
|
||||
.left-16 {
|
||||
left: 4rem;
|
||||
}
|
||||
.isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
.isolation-auto {
|
||||
isolation: auto;
|
||||
}
|
||||
.z-30 {
|
||||
z-index: 30;
|
||||
}
|
||||
.order-last {
|
||||
order: 9999;
|
||||
}
|
||||
.order-2 {
|
||||
order: 2;
|
||||
}
|
||||
.col-span-3 {
|
||||
grid-column: span 3 / span 3;
|
||||
}
|
||||
.col-start-1 {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
.col-end-4 {
|
||||
grid-column-end: 4;
|
||||
}
|
||||
.row-span-2 {
|
||||
grid-row: span 2 / span 2;
|
||||
}
|
||||
.row-start-3 {
|
||||
grid-row-start: 3;
|
||||
}
|
||||
.row-end-5 {
|
||||
grid-row-end: 5;
|
||||
}
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
.clear-left {
|
||||
clear: left;
|
||||
}
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
.my-2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.mt-0 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.mr-1 {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.box-border {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.inline-grid {
|
||||
display: inline-grid;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
.max-h-screen {
|
||||
max-height: 100vh;
|
||||
}
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.w-12 {
|
||||
width: 3rem;
|
||||
}
|
||||
.min-w-min {
|
||||
min-width: min-content;
|
||||
}
|
||||
.max-w-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.flex-grow-0 {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.table-fixed {
|
||||
table-layout: fixed;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.transform {
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y))
|
||||
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
|
||||
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
.transform-gpu {
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate))
|
||||
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
||||
scaleY(var(--tw-scale-y));
|
||||
}
|
||||
.origin-top-right {
|
||||
transform-origin: top right;
|
||||
}
|
||||
.translate-x-5 {
|
||||
--tw-translate-x: 1.25rem;
|
||||
}
|
||||
.-translate-x-4 {
|
||||
--tw-translate-x: -1rem;
|
||||
}
|
||||
.translate-y-6 {
|
||||
--tw-translate-y: 1.5rem;
|
||||
}
|
||||
.-translate-x-3 {
|
||||
--tw-translate-x: -0.75rem;
|
||||
}
|
||||
.rotate-3 {
|
||||
--tw-rotate: 3deg;
|
||||
}
|
||||
.skew-y-12 {
|
||||
--tw-skew-y: 12deg;
|
||||
}
|
||||
.skew-x-12 {
|
||||
--tw-skew-x: 12deg;
|
||||
}
|
||||
.scale-95 {
|
||||
--tw-scale-x: 0.95;
|
||||
--tw-scale-y: 0.95;
|
||||
}
|
||||
.animate-none {
|
||||
animation: none;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-none {
|
||||
user-select: none;
|
||||
}
|
||||
.resize-none {
|
||||
resize: none;
|
||||
}
|
||||
.list-inside {
|
||||
list-style-position: inside;
|
||||
}
|
||||
.list-disc {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.appearance-none {
|
||||
appearance: none;
|
||||
}
|
||||
.auto-cols-min {
|
||||
grid-auto-columns: min-content;
|
||||
}
|
||||
.grid-flow-row {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
.auto-rows-max {
|
||||
grid-auto-rows: max-content;
|
||||
}
|
||||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.grid-rows-3 {
|
||||
grid-template-rows: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.flex-row-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.place-content-start {
|
||||
place-content: start;
|
||||
}
|
||||
.place-items-end {
|
||||
place-items: end;
|
||||
}
|
||||
.content-center {
|
||||
align-content: center;
|
||||
}
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.justify-items-end {
|
||||
justify-items: end;
|
||||
}
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
.gap-x-2 {
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
.gap-y-3 {
|
||||
row-gap: 0.75rem;
|
||||
}
|
||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
.space-y-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 1;
|
||||
}
|
||||
.space-x-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 1;
|
||||
}
|
||||
.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-right-width: calc(2px * var(--tw-divide-x-reverse));
|
||||
border-left-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
.divide-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
.divide-x-0 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-right-width: calc(0px * var(--tw-divide-x-reverse));
|
||||
border-left-width: calc(0px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
.divide-y-0 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
.divide-dotted > :not([hidden]) ~ :not([hidden]) {
|
||||
border-style: dotted;
|
||||
}
|
||||
.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgba(229, 231, 235, var(--tw-divide-opacity));
|
||||
}
|
||||
.divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 0.5;
|
||||
}
|
||||
.place-self-center {
|
||||
place-self: center;
|
||||
}
|
||||
.self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
.justify-self-start {
|
||||
justify-self: start;
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
.overscroll-contain {
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.overflow-ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.break-words {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
.border-solid {
|
||||
border-style: solid;
|
||||
}
|
||||
.border-black {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
|
||||
}
|
||||
.border-opacity-10 {
|
||||
--tw-border-opacity: 0.1;
|
||||
}
|
||||
.bg-green-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-opacity-20 {
|
||||
--tw-bg-opacity: 0.2;
|
||||
}
|
||||
.bg-gradient-to-r {
|
||||
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||
}
|
||||
.from-red-300 {
|
||||
--tw-gradient-from: #fca5a5;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(252, 165, 165, 0));
|
||||
}
|
||||
.via-purple-200 {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), #ddd6fe,
|
||||
var(--tw-gradient-to, rgba(221, 214, 254, 0));
|
||||
}
|
||||
.to-blue-400 {
|
||||
--tw-gradient-to: #60a5fa;
|
||||
}
|
||||
.decoration-slice {
|
||||
box-decoration-break: slice;
|
||||
}
|
||||
.decoration-clone {
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
.bg-cover {
|
||||
background-size: cover;
|
||||
}
|
||||
.bg-local {
|
||||
background-attachment: local;
|
||||
}
|
||||
.bg-clip-border {
|
||||
background-clip: border-box;
|
||||
}
|
||||
.bg-top {
|
||||
background-position: top;
|
||||
}
|
||||
.bg-no-repeat {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.bg-origin-border {
|
||||
background-origin: border-box;
|
||||
}
|
||||
.bg-origin-padding {
|
||||
background-origin: padding-box;
|
||||
}
|
||||
.bg-origin-content {
|
||||
background-origin: content-box;
|
||||
}
|
||||
.fill-current {
|
||||
fill: currentColor;
|
||||
}
|
||||
.stroke-current {
|
||||
stroke: currentColor;
|
||||
}
|
||||
.stroke-2 {
|
||||
stroke-width: 2;
|
||||
}
|
||||
.object-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
.object-bottom {
|
||||
object-position: bottom;
|
||||
}
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.pb-3 {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
.pl-4 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.align-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.not-italic {
|
||||
font-style: normal;
|
||||
}
|
||||
.ordinal,
|
||||
.slashed-zero,
|
||||
.lining-nums,
|
||||
.oldstyle-nums,
|
||||
.proportional-nums,
|
||||
.tabular-nums,
|
||||
.diagonal-fractions,
|
||||
.stacked-fractions {
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
}
|
||||
.tabular-nums {
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
}
|
||||
.ordinal {
|
||||
--tw-ordinal: ordinal;
|
||||
}
|
||||
.diagonal-fractions {
|
||||
--tw-numeric-fraction: diagonal-fractions;
|
||||
}
|
||||
.leading-relaxed {
|
||||
line-height: 1.625;
|
||||
}
|
||||
.leading-5 {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
.tracking-tight {
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
.text-indigo-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(99, 102, 241, var(--tw-text-opacity));
|
||||
}
|
||||
.text-opacity-10 {
|
||||
--tw-text-opacity: 0.1;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.antialiased {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.placeholder-green-300::placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgba(110, 231, 183, var(--tw-placeholder-opacity));
|
||||
}
|
||||
.placeholder-opacity-60::placeholder {
|
||||
--tw-placeholder-opacity: 0.6;
|
||||
}
|
||||
.opacity-90 {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.bg-blend-darken {
|
||||
background-blend-mode: darken;
|
||||
}
|
||||
.bg-blend-difference {
|
||||
background-blend-mode: difference;
|
||||
}
|
||||
.mix-blend-multiply {
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
.mix-blend-saturation {
|
||||
mix-blend-mode: saturation;
|
||||
}
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.shadow-lg {
|
||||
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.outline-none {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.outline-black {
|
||||
outline: 2px dotted black;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.ring {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
|
||||
var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
|
||||
var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
.ring-4 {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
|
||||
var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width))
|
||||
var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
.ring-white {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgba(255, 255, 255, var(--tw-ring-opacity));
|
||||
}
|
||||
.ring-opacity-40 {
|
||||
--tw-ring-opacity: 0.4;
|
||||
}
|
||||
.ring-offset-2 {
|
||||
--tw-ring-offset-width: 2px;
|
||||
}
|
||||
.ring-offset-blue-300 {
|
||||
--tw-ring-offset-color: #93c5fd;
|
||||
}
|
||||
.filter {
|
||||
--tw-blur: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-brightness: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-contrast: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-grayscale: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-hue-rotate: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-invert: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-saturate: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-sepia: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-drop-shadow: var(--tw-empty, /*!*/ /*!*/);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
|
||||
var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
.filter-none {
|
||||
filter: none;
|
||||
}
|
||||
.blur-md {
|
||||
--tw-blur: blur(12px);
|
||||
}
|
||||
.brightness-150 {
|
||||
--tw-brightness: brightness(1.5);
|
||||
}
|
||||
.contrast-50 {
|
||||
--tw-contrast: contrast(0.5);
|
||||
}
|
||||
.drop-shadow-md {
|
||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgba(0, 0, 0, 0.07))
|
||||
drop-shadow(0 2px 2px rgba(0, 0, 0, 0.06));
|
||||
}
|
||||
.grayscale {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
}
|
||||
.hue-rotate-60 {
|
||||
--tw-hue-rotate: hue-rotate(60deg);
|
||||
}
|
||||
.invert {
|
||||
--tw-invert: invert(100%);
|
||||
}
|
||||
.saturate-200 {
|
||||
--tw-saturate: saturate(2);
|
||||
}
|
||||
.sepia {
|
||||
--tw-sepia: sepia(100%);
|
||||
}
|
||||
.backdrop-filter {
|
||||
--tw-backdrop-blur: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-brightness: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-contrast: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-grayscale: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-hue-rotate: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-invert: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-opacity: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-saturate: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-backdrop-sepia: var(--tw-empty, /*!*/ /*!*/);
|
||||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
|
||||
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
|
||||
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
}
|
||||
.backdrop-filter-none {
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.backdrop-blur-lg {
|
||||
--tw-backdrop-blur: blur(16px);
|
||||
}
|
||||
.backdrop-brightness-50 {
|
||||
--tw-backdrop-brightness: brightness(0.5);
|
||||
}
|
||||
.backdrop-contrast-0 {
|
||||
--tw-backdrop-contrast: contrast(0);
|
||||
}
|
||||
.backdrop-grayscale {
|
||||
--tw-backdrop-grayscale: grayscale(100%);
|
||||
}
|
||||
.backdrop-hue-rotate-90 {
|
||||
--tw-backdrop-hue-rotate: hue-rotate(90deg);
|
||||
}
|
||||
.backdrop-invert {
|
||||
--tw-backdrop-invert: invert(100%);
|
||||
}
|
||||
.backdrop-opacity-75 {
|
||||
--tw-backdrop-opacity: opacity(0.75);
|
||||
}
|
||||
.backdrop-saturate-150 {
|
||||
--tw-backdrop-saturate: saturate(1.5);
|
||||
}
|
||||
.backdrop-sepia {
|
||||
--tw-backdrop-sepia: sepia(100%);
|
||||
}
|
||||
.transition {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
|
||||
transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.delay-300 {
|
||||
transition-delay: 300ms;
|
||||
}
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
147
tests/jit/basic-usage.test.html
Normal file
147
tests/jit/basic-usage.test.html
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sr-only"></div>
|
||||
<div class="content-center"></div>
|
||||
<div class="items-start"></div>
|
||||
<div class="self-end"></div>
|
||||
<div class="animate-none"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="appearance-none"></div>
|
||||
<div class="bg-local"></div>
|
||||
<div class="bg-clip-border"></div>
|
||||
<div class="bg-green-500"></div>
|
||||
<div class="bg-gradient-to-r"></div>
|
||||
<div class="bg-opacity-20"></div>
|
||||
<div class="bg-top"></div>
|
||||
<div class="bg-no-repeat"></div>
|
||||
<div class="bg-cover"></div>
|
||||
<div class="bg-origin-border bg-origin-padding bg-origin-content"></div>
|
||||
<div class="border-collapse"></div>
|
||||
<div class="border-black"></div>
|
||||
<div class="border-opacity-10"></div>
|
||||
<div class="rounded-md"></div>
|
||||
<div class="border-solid"></div>
|
||||
<div class="border"></div>
|
||||
<div class="border-2"></div>
|
||||
<div class="shadow"></div>
|
||||
<div class="shadow-md"></div>
|
||||
<div class="shadow-lg"></div>
|
||||
<div class="decoration-clone decoration-slice"></div>
|
||||
<div class="box-border"></div>
|
||||
<div class="clear-left"></div>
|
||||
<div class="container"></div>
|
||||
<div class="cursor-pointer"></div>
|
||||
<div class="hidden inline-grid"></div>
|
||||
<div class="divide-gray-200"></div>
|
||||
<div class="divide-opacity-50"></div>
|
||||
<div class="divide-dotted"></div>
|
||||
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
|
||||
<div class="fill-current"></div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="flex-row-reverse"></div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="flex-grow-0"></div>
|
||||
<div class="flex-shrink"></div>
|
||||
<div class="flex-shrink-0"></div>
|
||||
<div class="flex-wrap"></div>
|
||||
<div class="float-right"></div>
|
||||
<div class="font-sans"></div>
|
||||
<div class="text-2xl"></div>
|
||||
<div class="antialiased"></div>
|
||||
<div class="not-italic"></div>
|
||||
<div class="tabular-nums ordinal diagonal-fractions"></div>
|
||||
<div class="font-medium"></div>
|
||||
<div class="gap-x-2 gap-y-3 gap-4"></div>
|
||||
<div class="from-red-300 via-purple-200 to-blue-400"></div>
|
||||
<div class="auto-cols-min"></div>
|
||||
<div class="grid-flow-row"></div>
|
||||
<div class="auto-rows-max"></div>
|
||||
<div class="col-span-3"></div>
|
||||
<div class="col-start-1"></div>
|
||||
<div class="col-end-4"></div>
|
||||
<div class="row-span-2"></div>
|
||||
<div class="row-start-3"></div>
|
||||
<div class="row-end-5"></div>
|
||||
<div class="grid-cols-4"></div>
|
||||
<div class="grid-rows-3"></div>
|
||||
<div class="h-16"></div>
|
||||
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
|
||||
<div class="isolate isolation-auto"></div>
|
||||
<div class="justify-center"></div>
|
||||
<div class="justify-items-end"></div>
|
||||
<div class="justify-self-start"></div>
|
||||
<div class="tracking-tight"></div>
|
||||
<div class="leading-relaxed leading-5"></div>
|
||||
<div class="list-inside"></div>
|
||||
<div class="list-disc"></div>
|
||||
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
|
||||
<div class="max-h-screen"></div>
|
||||
<div class="max-w-full"></div>
|
||||
<div class="min-h-0"></div>
|
||||
<div class="min-w-min"></div>
|
||||
<div class="object-cover"></div>
|
||||
<div class="object-bottom"></div>
|
||||
<div class="opacity-90"></div>
|
||||
<div class="bg-blend-darken bg-blend-difference"></div>
|
||||
<div class="mix-blend-multiply mix-blend-saturation"></div>
|
||||
<div class="order-last order-2"></div>
|
||||
<div class="outline-none outline-black"></div>
|
||||
<div class="overflow-hidden"></div>
|
||||
<div class="overscroll-contain"></div>
|
||||
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
|
||||
<div class="place-content-start"></div>
|
||||
<div class="placeholder-green-300"></div>
|
||||
<div class="placeholder-opacity-60"></div>
|
||||
<div class="place-items-end"></div>
|
||||
<div class="place-self-center"></div>
|
||||
<div class="pointer-events-none"></div>
|
||||
<div class="absolute"></div>
|
||||
<div class="resize-none"></div>
|
||||
<div class="ring-white"></div>
|
||||
<div class="ring-offset-blue-300"></div>
|
||||
<div class="ring-offset-2"></div>
|
||||
<div class="ring-opacity-40"></div>
|
||||
<div class="ring ring-4"></div>
|
||||
<div
|
||||
class="filter filter-none blur-md brightness-150 contrast-50 drop-shadow-md grayscale hue-rotate-60 invert saturate-200 sepia"
|
||||
></div>
|
||||
<div
|
||||
class="backdrop-filter backdrop-filter-none backdrop-blur-lg backdrop-brightness-50 backdrop-contrast-0 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-75 backdrop-saturate-150 backdrop-sepia"
|
||||
></div>
|
||||
<div class="rotate-3"></div>
|
||||
<div class="scale-95"></div>
|
||||
<div class="skew-y-12 skew-x-12"></div>
|
||||
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
|
||||
<div class="stroke-current"></div>
|
||||
<div class="stroke-2"></div>
|
||||
<div class="table-fixed"></div>
|
||||
<div class="text-center"></div>
|
||||
<div class="text-indigo-500"></div>
|
||||
<div class="underline"></div>
|
||||
<div class="text-opacity-10"></div>
|
||||
<div class="overflow-ellipsis truncate"></div>
|
||||
<div class="uppercase"></div>
|
||||
<div class="transform transform-gpu"></div>
|
||||
<div class="origin-top-right"></div>
|
||||
<div class="delay-300"></div>
|
||||
<div class="duration-200"></div>
|
||||
<div class="transition transition-all"></div>
|
||||
<div class="ease-in-out"></div>
|
||||
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
|
||||
<div class="select-none"></div>
|
||||
<div class="align-middle"></div>
|
||||
<div class="invisible"></div>
|
||||
<div class="whitespace-nowrap"></div>
|
||||
<div class="w-12"></div>
|
||||
<div class="break-words"></div>
|
||||
<div class="z-30"></div>
|
||||
</body>
|
||||
</html>
|
||||
33
tests/jit/basic-usage.test.js
Normal file
33
tests/jit/basic-usage.test.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('basic usage', () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './basic-usage.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './basic-usage.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
56
tests/jit/collapse-adjacent-rules.test.css
Normal file
56
tests/jit/collapse-adjacent-rules.test.css
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Proxima Nova';
|
||||
src: url('/fonts/ProximaNova.woff2') format('woff2'),
|
||||
url('/fonts/ProximaNova.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gilroy';
|
||||
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.sm\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.lg\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
23
tests/jit/collapse-adjacent-rules.test.html
Normal file
23
tests/jit/collapse-adjacent-rules.test.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="custom-component"></div>
|
||||
<div class="sm:custom-component"></div>
|
||||
<div class="md:custom-component"></div>
|
||||
<div class="lg:custom-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="sm:font-bold"></div>
|
||||
<div class="sm:text-center"></div>
|
||||
<div class="md:font-bold"></div>
|
||||
<div class="md:text-center"></div>
|
||||
<div class="lg:font-bold"></div>
|
||||
<div class="lg:text-center"></div>
|
||||
</body>
|
||||
</html>
|
||||
58
tests/jit/collapse-adjacent-rules.test.js
Normal file
58
tests/jit/collapse-adjacent-rules.test.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('collapse adjacent rules', () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './collapse-adjacent-rules.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("/fonts/Inter.woff2") format("woff2"),
|
||||
url("/fonts/Inter.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Gilroy";
|
||||
src: url("/fonts/Gilroy.woff2") format("woff2"),
|
||||
url("/fonts/Gilroy.woff") format("woff");
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
@font-face {
|
||||
font-family: "Poppins";
|
||||
src: url("/fonts/Poppins.woff2") format("woff2"),
|
||||
url("/fonts/Poppins.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Proxima Nova";
|
||||
src: url("/fonts/ProximaNova.woff2") format("woff2"),
|
||||
url("/fonts/ProximaNova.woff") format("woff");
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './collapse-adjacent-rules.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
11
tests/jit/context-reuse.test.html
Normal file
11
tests/jit/context-reuse.test.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
17
tests/jit/context-reuse.test.js
Normal file
17
tests/jit/context-reuse.test.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const path = require('path')
|
||||
const process = require('child_process')
|
||||
const { promisify } = require('util')
|
||||
const exec = promisify(process.exec)
|
||||
|
||||
test.skip('a build re-uses the context across multiple files with the same config', async () => {
|
||||
const filepath = path.resolve(__dirname, './context-reuse.worker.js')
|
||||
|
||||
const { stdout } = await exec(`node ${filepath}`)
|
||||
|
||||
const results = JSON.parse(stdout.trim())
|
||||
|
||||
expect(results[0].activeContexts).toBe(1)
|
||||
expect(results[1].activeContexts).toBe(1)
|
||||
expect(results[2].activeContexts).toBe(1)
|
||||
expect(results[3].activeContexts).toBe(1)
|
||||
})
|
||||
48
tests/jit/context-reuse.worker.js
Normal file
48
tests/jit/context-reuse.worker.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const postcss = require('postcss')
|
||||
const tailwind = require('../index.js')
|
||||
const sharedState = require('../lib/sharedState.js')
|
||||
const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
|
||||
|
||||
function run(input, config = {}, from = null) {
|
||||
from = from || path.resolve(__filename)
|
||||
|
||||
return postcss(tailwind(config)).process(input, { from })
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
let messages = []
|
||||
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './context-reuse.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let from = path.resolve(__filename)
|
||||
|
||||
fs.writeFileSync(configPath, `module.exports = ${JSON.stringify(config)};`)
|
||||
|
||||
let results = [
|
||||
await run(`@tailwind utilities;`, configPath, `${from}?id=1`),
|
||||
await run(`body { @apply bg-blue-400; }`, configPath, `${from}?id=2`),
|
||||
await run(`body { @apply text-red-400; }`, configPath, `${from}?id=3`),
|
||||
await run(`body { @apply mb-4; }`, configPath, `${from}?id=4`),
|
||||
]
|
||||
|
||||
results.forEach(() => {
|
||||
messages.push({
|
||||
activeContexts: sharedState.contextSourcesMap.size,
|
||||
})
|
||||
})
|
||||
|
||||
process.stdout.write(JSON.stringify(messages))
|
||||
}
|
||||
|
||||
runTest().finally(() => {
|
||||
fs.unlinkSync(configPath)
|
||||
})
|
||||
17
tests/jit/custom-extractors.test.css
Normal file
17
tests/jit/custom-extractors.test.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
||||
}
|
||||
.text-indigo-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(99, 102, 241, var(--tw-text-opacity));
|
||||
}
|
||||
14
tests/jit/custom-extractors.test.html
Normal file
14
tests/jit/custom-extractors.test.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="text-indigo-500 bg-white">hello world</div>
|
||||
<span>text-red-500 shouldn't appear in the output</span>
|
||||
</body>
|
||||
</html>
|
||||
67
tests/jit/custom-extractors.test.js
Normal file
67
tests/jit/custom-extractors.test.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
function run(input, config = {}) {
|
||||
jest.resetModules()
|
||||
const tailwind = require('../../src/jit/index.js').default
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
function customExtractor(content) {
|
||||
const matches = content.match(/class="([^"]+)"/)
|
||||
return matches ? matches[1].split(/\s+/) : []
|
||||
}
|
||||
|
||||
const css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
const expectedPath = path.resolve(__dirname, './custom-extractors.test.css')
|
||||
const expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
test('defaultExtractor', () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: {
|
||||
content: [path.resolve(__dirname, './custom-extractors.test.html')],
|
||||
options: {
|
||||
defaultExtractor: customExtractor,
|
||||
},
|
||||
},
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('extractors array', () => {
|
||||
let config = {
|
||||
purge: {
|
||||
content: [path.resolve(__dirname, './custom-extractors.test.html')],
|
||||
options: {
|
||||
extractors: [
|
||||
{
|
||||
extractor: customExtractor,
|
||||
extensions: ['html'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
mode: 'jit',
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
19
tests/jit/custom-separator.test.css
Normal file
19
tests/jit/custom-separator.test.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
.group_hover .group-hover_focus-within_text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
[dir='rtl'] .rtl_active_text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe_hover_text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.dark .dark_focus_text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md_hover_text-right:hover {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
5
tests/jit/custom-separator.test.html
Normal file
5
tests/jit/custom-separator.test.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<div class="md_hover_text-right"></div>
|
||||
<div class="motion-safe_hover_text-center"></div>
|
||||
<div class="dark_focus_text-left"></div>
|
||||
<div class="group-hover_focus-within_text-left"></div>
|
||||
<div class="rtl_active_text-center"></div>
|
||||
31
tests/jit/custom-separator.test.js
Normal file
31
tests/jit/custom-separator.test.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('custom separator', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './custom-separator.test.html')],
|
||||
separator: '_',
|
||||
corePlugins: {},
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './custom-separator.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
52
tests/jit/import-syntax.test.css
Normal file
52
tests/jit/import-syntax.test.css
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
5
tests/jit/import-syntax.test.html
Normal file
5
tests/jit/import-syntax.test.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<h1>Hello world!</h1>
|
||||
<div class="container"></div>
|
||||
<div class="mt-6"></div>
|
||||
<div class="bg-black"></div>
|
||||
<div class="md:hover:text-center"></div>
|
||||
41
tests/jit/import-syntax.test.js
Normal file
41
tests/jit/import-syntax.test.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('using @import instead of @tailwind', () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './import-syntax.test.html')],
|
||||
mode: 'jit',
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addBase }) {
|
||||
addBase({
|
||||
h1: {
|
||||
fontSize: '32px',
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './import-syntax.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
85
tests/jit/important-boolean.test.css
Normal file
85
tests/jit/important-boolean.test.css
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
button: yes !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite !important;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
.group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left !important;
|
||||
}
|
||||
[dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:hover\:text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
.dark .dark\:focus\:text-left:focus {
|
||||
text-align: left !important;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-right:hover {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
12
tests/jit/important-boolean.test.html
Normal file
12
tests/jit/important-boolean.test.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
73
tests/jit/important-boolean.test.js
Normal file
73
tests/jit/important-boolean.test.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('important boolean', () => {
|
||||
let config = {
|
||||
important: true,
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './important-boolean.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './important-boolean.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
53
tests/jit/important-modifier-prefix.test.css
Normal file
53
tests/jit/important-modifier-prefix.test.css
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.\!tw-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.\!tw-container {
|
||||
max-width: 640px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.\!tw-container {
|
||||
max-width: 768px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.\!tw-container {
|
||||
max-width: 1024px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.\!tw-container {
|
||||
max-width: 1280px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.\!tw-container {
|
||||
max-width: 1536px !important;
|
||||
}
|
||||
}
|
||||
.\!tw-font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.hover\:\!tw-text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!tw-opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!tw-float-right:focus:disabled {
|
||||
float: right !important;
|
||||
}
|
||||
}
|
||||
5
tests/jit/important-modifier-prefix.test.html
Normal file
5
tests/jit/important-modifier-prefix.test.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<div class="!tw-container"></div>
|
||||
<div class="!tw-font-bold"></div>
|
||||
<div class="hover:!tw-text-center"></div>
|
||||
<div class="lg:!tw-opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!tw-float-right"></div>
|
||||
36
tests/jit/important-modifier-prefix.test.js
Normal file
36
tests/jit/important-modifier-prefix.test.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('important modifier with prefix', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './important-modifier-prefix.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './important-modifier-prefix.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
53
tests/jit/important-modifier.test.css
Normal file
53
tests/jit/important-modifier.test.css
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.\!container {
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.\!container {
|
||||
max-width: 640px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.\!container {
|
||||
max-width: 768px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.\!container {
|
||||
max-width: 1024px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.\!container {
|
||||
max-width: 1280px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.\!container {
|
||||
max-width: 1536px !important;
|
||||
}
|
||||
}
|
||||
.\!font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.hover\:\!text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!float-right:focus:disabled {
|
||||
float: right !important;
|
||||
}
|
||||
}
|
||||
5
tests/jit/important-modifier.test.html
Normal file
5
tests/jit/important-modifier.test.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<div class="!container"></div>
|
||||
<div class="!font-bold"></div>
|
||||
<div class="hover:!text-center"></div>
|
||||
<div class="lg:!opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!float-right"></div>
|
||||
35
tests/jit/important-modifier.test.js
Normal file
35
tests/jit/important-modifier.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('important modifier', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './important-modifier.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './important-modifier.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
85
tests/jit/important-selector.test.css
Normal file
85
tests/jit/important-selector.test.css
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
#app .btn {
|
||||
button: yes;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#app .animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
#app .font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
#app .group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
#app [dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
#app .motion-safe\:hover\:text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
#app .dark .dark\:focus\:text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#app .md\:hover\:text-right:hover {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
12
tests/jit/important-selector.test.html
Normal file
12
tests/jit/important-selector.test.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
73
tests/jit/important-selector.test.js
Normal file
73
tests/jit/important-selector.test.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('important selector', () => {
|
||||
let config = {
|
||||
important: '#app',
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './important-selector.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './important-selector.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
568
tests/jit/kitchen-sink.test.css
Normal file
568
tests/jit/kitchen-sink.test.css
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
.theme-test {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
color: #3b82f6;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.screen-test {
|
||||
color: purple;
|
||||
}
|
||||
}
|
||||
.apply-1 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.apply-2 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.apply-test {
|
||||
margin-top: 1.5rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(236, 72, 153, var(--tw-bg-opacity));
|
||||
}
|
||||
.apply-test:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
.apply-test:focus:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.apply-test {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
.apply-test:focus:nth-child(even) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(251, 207, 232, var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
.apply-components {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.apply-components {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.apply-components {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.apply-components {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.apply-components {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.apply-components {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.apply-components {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.drop-empty-rules:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
.group:hover .apply-group {
|
||||
font-weight: 700;
|
||||
}
|
||||
.dark .apply-dark-mode {
|
||||
font-weight: 700;
|
||||
}
|
||||
.apply-with-existing:hover {
|
||||
font-weight: 400;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.apply-with-existing:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
.multiple,
|
||||
.selectors {
|
||||
font-weight: 700;
|
||||
}
|
||||
.group:hover .multiple,
|
||||
.group:hover .selectors {
|
||||
font-weight: 400;
|
||||
}
|
||||
.list > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
.nested {
|
||||
.example {
|
||||
font-weight: 700;
|
||||
}
|
||||
.example:hover {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.apply-order-a {
|
||||
margin: 1.25rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.apply-order-b {
|
||||
margin: 1.25rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.dark .group:hover .apply-dark-group-example-a {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.group:active .crazy-example:focus {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
h1:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
div {
|
||||
background: #654321;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.aspect-w-1,
|
||||
.aspect-w-2,
|
||||
.aspect-w-3,
|
||||
.aspect-w-4,
|
||||
.aspect-w-5,
|
||||
.aspect-w-6,
|
||||
.aspect-w-7,
|
||||
.aspect-w-8,
|
||||
.aspect-w-9,
|
||||
.aspect-w-10,
|
||||
.aspect-w-11,
|
||||
.aspect-w-12,
|
||||
.aspect-w-13,
|
||||
.aspect-w-14,
|
||||
.aspect-w-15,
|
||||
.aspect-w-16 {
|
||||
position: relative;
|
||||
padding-bottom: calc(var(--tw-aspect-h) / var(--tw-aspect-w) * 100%);
|
||||
}
|
||||
.aspect-w-1 > *,
|
||||
.aspect-w-2 > *,
|
||||
.aspect-w-3 > *,
|
||||
.aspect-w-4 > *,
|
||||
.aspect-w-5 > *,
|
||||
.aspect-w-6 > *,
|
||||
.aspect-w-7 > *,
|
||||
.aspect-w-8 > *,
|
||||
.aspect-w-9 > *,
|
||||
.aspect-w-10 > *,
|
||||
.aspect-w-11 > *,
|
||||
.aspect-w-12 > *,
|
||||
.aspect-w-13 > *,
|
||||
.aspect-w-14 > *,
|
||||
.aspect-w-15 > *,
|
||||
.aspect-w-16 > * {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.aspect-w-1 {
|
||||
--tw-aspect-w: 1;
|
||||
}
|
||||
.aspect-w-3 {
|
||||
--tw-aspect-w: 3;
|
||||
}
|
||||
.aspect-h-2 {
|
||||
--tw-aspect-h: 2;
|
||||
}
|
||||
.aspect-h-4 {
|
||||
--tw-aspect-h: 4;
|
||||
}
|
||||
.test-apply-font-variant {
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
--tw-ordinal: ordinal;
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
}
|
||||
.custom-component {
|
||||
background: #123456;
|
||||
}
|
||||
* {
|
||||
padding: 5px;
|
||||
}
|
||||
.foo .bg-black {
|
||||
appearance: none;
|
||||
}
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.transform {
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y))
|
||||
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
|
||||
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
.scale-50 {
|
||||
--tw-scale-x: 0.5;
|
||||
--tw-scale-y: 0.5;
|
||||
}
|
||||
.grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
|
||||
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
|
||||
}
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-green-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
.bg-gradient-to-r {
|
||||
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||
}
|
||||
.bg-hero--home-1 {
|
||||
background-image: url('/images/homepage-1.jpg');
|
||||
}
|
||||
.from-foo {
|
||||
--tw-gradient-from: #bada55;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(186, 218, 85, 0));
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.magic-none {
|
||||
magic: none;
|
||||
}
|
||||
.magic-tons {
|
||||
magic: tons;
|
||||
}
|
||||
.custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
* {
|
||||
margin: 10px;
|
||||
}
|
||||
.first\:pt-0:first-child {
|
||||
padding-top: 0px;
|
||||
}
|
||||
.hover\:container:hover {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.hover\:container:hover {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.hover\:container:hover {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.hover\:container:hover {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.hover\:container:hover {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.hover\:container:hover {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.hover\:scale-75:hover {
|
||||
--tw-scale-x: 0.75;
|
||||
--tw-scale-y: 0.75;
|
||||
}
|
||||
.hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hover\:shadow-lg:hover {
|
||||
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.hover\:custom-util:hover {
|
||||
background: #abcdef;
|
||||
}
|
||||
.focus\:font-normal:focus {
|
||||
font-weight: 400;
|
||||
}
|
||||
.focus\:ring-2:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
|
||||
var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width))
|
||||
var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
.focus\:ring-blue-500:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgba(59, 130, 246, var(--tw-ring-opacity));
|
||||
}
|
||||
.focus\:hover\:font-light:focus:hover {
|
||||
font-weight: 300;
|
||||
}
|
||||
.disabled\:font-bold:disabled {
|
||||
font-weight: 700;
|
||||
}
|
||||
.group:hover .group-hover\:opacity-100 {
|
||||
opacity: 1;
|
||||
}
|
||||
.group:hover .group-hover\:custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
.group:active .group-active\:opacity-10 {
|
||||
opacity: 0.1;
|
||||
}
|
||||
.foo\:custom-util {
|
||||
background: #abcdef !important;
|
||||
}
|
||||
.foo\:hover\:custom-util:hover {
|
||||
background: #abcdef !important;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:transition {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
|
||||
transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.motion-safe\:custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\:transition {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
|
||||
transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
}
|
||||
.dark .dark\:custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.sm\:container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.sm\:container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.sm\:container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.sm\:container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.sm\:ordinal,
|
||||
.sm\:slashed-zero,
|
||||
.sm\:lining-nums,
|
||||
.sm\:oldstyle-nums,
|
||||
.sm\:proportional-nums,
|
||||
.sm\:tabular-nums,
|
||||
.sm\:diagonal-fractions,
|
||||
.sm\:stacked-fractions {
|
||||
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
|
||||
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
|
||||
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
|
||||
}
|
||||
.sm\:tabular-nums {
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
}
|
||||
.sm\:custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.md\:container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.md\:container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.md\:container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.md\:container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.md\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.md\:opacity-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.md\:shadow-sm {
|
||||
--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.md\:hover\:opacity-20:hover {
|
||||
opacity: 0.2;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.md\:motion-safe\:hover\:transition:hover {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
|
||||
transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active {
|
||||
background: #abcdef !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.md\:sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait) {
|
||||
.portrait\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) and (max-width: 1535px) {
|
||||
.range\:text-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
|
||||
.multi\:text-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
76
tests/jit/kitchen-sink.test.html
Normal file
76
tests/jit/kitchen-sink.test.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="portrait:text-center range:text-right multi:text-left"></div>
|
||||
<div
|
||||
class="container hover:container sm:container md:container text-center sm:text-center md:text-center"
|
||||
></div>
|
||||
<div class="grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
|
||||
<div class="test-apply-font-variant"></div>
|
||||
<div class="mt-6"></div>
|
||||
<div class="bg-black"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="hover:custom-util"></div>
|
||||
<div class="group-hover:custom-util"></div>
|
||||
<div class="foo:custom-util"></div>
|
||||
<div class="foo:hover:custom-util"></div>
|
||||
<div class="sm:custom-util"></div>
|
||||
<div class="dark:custom-util"></div>
|
||||
<div class="motion-safe:custom-util"></div>
|
||||
<div class="md:dark:motion-safe:foo:active:custom-util"></div>
|
||||
<div class="aspect-w-1 aspect-h-2"></div>
|
||||
<div class="aspect-w-3 aspect-h-4"></div>
|
||||
<div class="magic-none magic-tons"></div>
|
||||
<div class="focus:font-normal"></div>
|
||||
<div class="font-medium"></div>
|
||||
<div class="bg-gradient-to-r from-foo"></div>
|
||||
<div class="custom-component custom-util"></div>
|
||||
<div class="bg-opacity-50"></div>
|
||||
<div class="focus:ring-2 focus:ring-blue-500"></div>
|
||||
<div class="hover:font-bold"></div>
|
||||
<div class="disabled:font-bold"></div>
|
||||
<div class="focus:hover:font-light"></div>
|
||||
<div class="first:pt-0"></div>
|
||||
<div class="container"></div>
|
||||
<div class="bg-hero--home-1"></div>
|
||||
<div class="group-hover:opacity-100"></div>
|
||||
<div class="group-active:opacity-10"></div>
|
||||
<div class="sm:motion-safe:group-active:focus:opacity-10"></div>
|
||||
<div class="motion-safe:transition"></div>
|
||||
<div class="motion-reduce:transition"></div>
|
||||
<div class="md:motion-safe:hover:transition"></div>
|
||||
<div class="md:sm:text-center shadow-sm md:shadow-sm"></div>
|
||||
<div class="md:sm:text-center shadow-sm md:shadow-sm"></div>
|
||||
<div class="bg-green-500 md:opacity-50 md:hover:opacity-20 sm:tabular-nums"></div>
|
||||
<div class="text-center shadow-md hover:shadow-lg transform scale-50 hover:scale-75"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
defineComponent({
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
things: Array /* PropType<string[]> */,
|
||||
},
|
||||
setup: () => {
|
||||
const count = ref(0)
|
||||
// Weird regex-looking stuff that once caused a stack overflow in candidatePermutations
|
||||
const pattern = ' ]-[] '
|
||||
return {
|
||||
count,
|
||||
stuff: [] /* string[] | undefined */,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
169
tests/jit/kitchen-sink.test.js
Normal file
169
tests/jit/kitchen-sink.test.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('it works', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './kitchen-sink.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
portrait: { raw: '(orientation: portrait)' },
|
||||
range: { min: '1280px', max: '1535px' },
|
||||
multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
|
||||
},
|
||||
gradientColorStops: {
|
||||
foo: '#bada55',
|
||||
},
|
||||
backgroundImage: {
|
||||
'hero--home-1': "url('/images/homepage-1.jpg')",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
function ({ addVariant }) {
|
||||
addVariant(
|
||||
'foo',
|
||||
({ container }) => {
|
||||
container.walkRules((rule) => {
|
||||
rule.selector = `.foo\\:${rule.selector.slice(1)}`
|
||||
rule.walkDecls((decl) => {
|
||||
decl.important = true
|
||||
})
|
||||
})
|
||||
},
|
||||
{ before: 'sm' }
|
||||
)
|
||||
},
|
||||
function ({ addUtilities, addBase, theme }) {
|
||||
addBase({
|
||||
h1: {
|
||||
fontSize: theme('fontSize.2xl'),
|
||||
fontWeight: theme('fontWeight.bold'),
|
||||
'&:first-child': {
|
||||
marginTop: '0px',
|
||||
},
|
||||
},
|
||||
})
|
||||
addUtilities(
|
||||
{
|
||||
'.magic-none': {
|
||||
magic: 'none',
|
||||
},
|
||||
'.magic-tons': {
|
||||
magic: 'tons',
|
||||
},
|
||||
},
|
||||
['responsive', 'hover']
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@layer utilities {
|
||||
.custom-util {
|
||||
background: #abcdef;
|
||||
}
|
||||
* {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
@layer components {
|
||||
.test-apply-font-variant {
|
||||
@apply ordinal tabular-nums;
|
||||
}
|
||||
.custom-component {
|
||||
background: #123456;
|
||||
}
|
||||
* {
|
||||
padding: 5px;
|
||||
}
|
||||
.foo .bg-black {
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
div {
|
||||
background: #654321;
|
||||
}
|
||||
}
|
||||
.theme-test {
|
||||
font-family: theme('fontFamily.sans');
|
||||
color: theme('colors.blue.500');
|
||||
}
|
||||
@screen lg {
|
||||
.screen-test {
|
||||
color: purple;
|
||||
}
|
||||
}
|
||||
.apply-1 {
|
||||
@apply mt-6;
|
||||
}
|
||||
.apply-2 {
|
||||
@apply mt-6;
|
||||
}
|
||||
.apply-test {
|
||||
@apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200;
|
||||
}
|
||||
.apply-components {
|
||||
@apply container mx-auto;
|
||||
}
|
||||
.drop-empty-rules {
|
||||
@apply hover:font-bold;
|
||||
}
|
||||
.apply-group {
|
||||
@apply group-hover:font-bold;
|
||||
}
|
||||
.apply-dark-mode {
|
||||
@apply dark:font-bold;
|
||||
}
|
||||
.apply-with-existing:hover {
|
||||
@apply font-normal sm:bg-green-500;
|
||||
}
|
||||
.multiple, .selectors {
|
||||
@apply font-bold group-hover:font-normal;
|
||||
}
|
||||
.list {
|
||||
@apply space-x-4;
|
||||
}
|
||||
.nested {
|
||||
.example {
|
||||
@apply font-bold hover:font-normal;
|
||||
}
|
||||
}
|
||||
.apply-order-a {
|
||||
@apply m-5 mt-6;
|
||||
}
|
||||
.apply-order-b {
|
||||
@apply mt-6 m-5;
|
||||
}
|
||||
.apply-dark-group-example-a {
|
||||
@apply dark:group-hover:bg-green-500;
|
||||
}
|
||||
.crazy-example {
|
||||
@apply sm:motion-safe:group-active:focus:opacity-10;
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './kitchen-sink.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
33
tests/jit/modify-selectors.test.css
Normal file
33
tests/jit/modify-selectors.test.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:visited\:markdown:visited > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.foo .sm\:foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.foo .md\:foo\:focus\:font-bold:focus {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.foo .lg\:foo\:disabled\:markdown:disabled > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
17
tests/jit/modify-selectors.test.html
Normal file
17
tests/jit/modify-selectors.test.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="font-bold"></div>
|
||||
<div class="foo:font-bold"></div>
|
||||
<div class="foo:hover:font-bold"></div>
|
||||
<div class="sm:foo:font-bold"></div>
|
||||
<div class="md:foo:focus:font-bold"></div>
|
||||
<div class="markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:visited:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="lg:foo:disabled:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
53
tests/jit/modify-selectors.test.js
Normal file
53
tests/jit/modify-selectors.test.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('modify selectors', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './modify-selectors.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ selector }) => {
|
||||
return selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `foo${separator}${classNode.value}`
|
||||
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './modify-selectors.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
3
tests/jit/mutable.test.css
Normal file
3
tests/jit/mutable.test.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.bg-foo {
|
||||
background-image: url("./foo.png");
|
||||
}
|
||||
13
tests/jit/mutable.test.html
Normal file
13
tests/jit/mutable.test.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-foo"></div>
|
||||
</body>
|
||||
</html>
|
||||
60
tests/jit/mutable.test.js
Normal file
60
tests/jit/mutable.test.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function pluginThatMutatesRules() {
|
||||
return (root) => {
|
||||
root.walkRules((rule) => {
|
||||
rule.nodes
|
||||
.filter((node) => node.prop === 'background-image')
|
||||
.forEach((node) => {
|
||||
node.value = 'url("./bar.png")'
|
||||
})
|
||||
|
||||
return rule
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test.only('plugins mutating rules after tailwind doesnt break it', async () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './mutable.test.html')],
|
||||
mode: 'jit',
|
||||
theme: {
|
||||
backgroundImage: {
|
||||
foo: 'url("./foo.png")',
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities;`
|
||||
|
||||
function checkResult(result) {
|
||||
let expectedPath = path.resolve(__dirname, './mutable.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
}
|
||||
|
||||
// Verify the first run produces the expected result
|
||||
let firstRun = await run(css, config)
|
||||
checkResult(firstRun)
|
||||
|
||||
// Outside of the context of tailwind jit more postcss plugins may operate on the AST:
|
||||
// In this case we have a plugin that mutates rules directly
|
||||
await postcss([pluginThatMutatesRules()]).process(firstRun, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
|
||||
// Verify subsequent runs don't produce mutated rules
|
||||
let secondRun = await run(css, config)
|
||||
checkResult(secondRun)
|
||||
})
|
||||
24
tests/jit/opacity.test.css
Normal file
24
tests/jit/opacity.test.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.divide-black > :not([hidden]) ~ :not([hidden]) {
|
||||
border-color: #000;
|
||||
}
|
||||
.border-black {
|
||||
border-color: #000;
|
||||
}
|
||||
.bg-black {
|
||||
background-color: #000;
|
||||
}
|
||||
.text-black {
|
||||
color: #000;
|
||||
}
|
||||
.placeholder-black::placeholder {
|
||||
color: #000;
|
||||
}
|
||||
17
tests/jit/opacity.test.html
Normal file
17
tests/jit/opacity.test.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="divide-black"></div>
|
||||
<div class="border-black"></div>
|
||||
<div class="bg-black"></div>
|
||||
<div class="text-black"></div>
|
||||
<div class="placeholder-black"></div>
|
||||
</body>
|
||||
</html>
|
||||
39
tests/jit/opacity.test.js
Normal file
39
tests/jit/opacity.test.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, { from: path.resolve(__filename) })
|
||||
}
|
||||
|
||||
test('opacity', () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
darkMode: 'class',
|
||||
purge: [path.resolve(__dirname, './opacity.test.html')],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
backgroundOpacity: false,
|
||||
borderOpacity: false,
|
||||
divideOpacity: false,
|
||||
placeholderOpacity: false,
|
||||
textOpacity: false,
|
||||
},
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './opacity.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
9
tests/jit/prefix.fn.test.css
Normal file
9
tests/jit/prefix.fn.test.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.tw-ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.tw-align-bottom {
|
||||
vertical-align: bottom
|
||||
}
|
||||
3
tests/jit/prefix.fn.test.html
Normal file
3
tests/jit/prefix.fn.test.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<div class="flex"></div>
|
||||
<div class="tw-align-bottom"></div>
|
||||
<div class="tw-ml-1"></div>
|
||||
37
tests/jit/prefix.fn.test.js
Normal file
37
tests/jit/prefix.fn.test.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('prefix fn', () => {
|
||||
let config = {
|
||||
prefix(selector) {
|
||||
if (['.align-bottom', '.ml'].some((prefix) => selector.startsWith(prefix))) {
|
||||
return 'tw-'
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
purge: [path.resolve(__dirname, './prefix.fn.test.html')],
|
||||
mode: 'jit',
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './prefix.fn.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
86
tests/jit/prefix.test.css
Normal file
86
tests/jit/prefix.test.css
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.tw-container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.tw-container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.tw-container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.tw-container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.tw-container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.tw-container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.tw-btn-prefix {
|
||||
button: yes;
|
||||
}
|
||||
.btn-no-prefix {
|
||||
button: yes;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.tw-dark .tw-group:hover .custom-component {
|
||||
font-weight: 400;
|
||||
}
|
||||
.tw--ml-4 {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
.tw-font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.tw-custom-util-prefix {
|
||||
button: no;
|
||||
}
|
||||
.custom-util-no-prefix {
|
||||
button: no;
|
||||
}
|
||||
.tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
[dir='rtl'] .rtl\:active\:tw-text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:hover\:tw-text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.tw-dark .dark\:focus\:tw-text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:tw--ml-5 {
|
||||
margin-left: -1.25rem;
|
||||
}
|
||||
.md\:hover\:tw--ml-6:hover {
|
||||
margin-left: -1.5rem;
|
||||
}
|
||||
.md\:hover\:tw-text-right:hover {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
17
tests/jit/prefix.test.html
Normal file
17
tests/jit/prefix.test.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="tw--ml-4"></div>
|
||||
<div class="md:tw--ml-5"></div>
|
||||
<div class="md:hover:tw--ml-6"></div>
|
||||
<div class="tw-container"></div>
|
||||
<div class="btn-no-prefix"></div>
|
||||
<div class="tw-btn-prefix"></div>
|
||||
<div class="tw-custom-util-prefix"></div>
|
||||
<div class="custom-util-no-prefix"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="tw-custom-component-prefix"></div>
|
||||
<div class="custom-component-no-prefix"></div>
|
||||
<div class="tw-font-bold"></div>
|
||||
<div class="md:hover:tw-text-right"></div>
|
||||
<div class="motion-safe:hover:tw-text-center"></div>
|
||||
<div class="dark:focus:tw-text-left"></div>
|
||||
<div class="group-hover:focus-within:tw-text-left"></div>
|
||||
<div class="rtl:active:tw-text-center"></div>
|
||||
74
tests/jit/prefix.test.js
Normal file
74
tests/jit/prefix.test.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './prefix.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents({
|
||||
'.btn-prefix': {
|
||||
button: 'yes',
|
||||
},
|
||||
})
|
||||
addComponents(
|
||||
{
|
||||
'.btn-no-prefix': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
{ respectPrefix: false }
|
||||
)
|
||||
addUtilities({
|
||||
'.custom-util-prefix': {
|
||||
button: 'no',
|
||||
},
|
||||
})
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util-no-prefix': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectPrefix: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply tw-font-bold dark:group-hover:tw-font-normal;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
@layer utilites {
|
||||
.custom-utility {
|
||||
foo: bar;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './prefix.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
65
tests/jit/responsive-and-variants-atrules.test.css
Normal file
65
tests/jit/responsive-and-variants-atrules.test.css
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.responsive-in-components {
|
||||
color: blue;
|
||||
}
|
||||
.variants-in-components {
|
||||
color: red;
|
||||
}
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
}
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
}
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
}
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:focus\:responsive-in-components:focus {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-components:focus {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-components:focus {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-in-utilities:focus {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-utilities:focus {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-utilities:focus {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-at-root:focus {
|
||||
color: white;
|
||||
}
|
||||
.md\:focus\:variants-at-root:focus {
|
||||
color: orange;
|
||||
}
|
||||
.md\:focus\:both-at-root:focus {
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
18
tests/jit/responsive-and-variants-atrules.test.html
Normal file
18
tests/jit/responsive-and-variants-atrules.test.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div class="responsive-in-utilities"></div>
|
||||
<div class="variants-in-utilities"></div>
|
||||
<div class="both-in-utilities"></div>
|
||||
<div class="responsive-at-root"></div>
|
||||
<div class="variants-at-root"></div>
|
||||
<div class="both-at-root"></div>
|
||||
<div class="responsive-in-components"></div>
|
||||
<div class="variants-in-components"></div>
|
||||
<div class="both-in-components"></div>
|
||||
<div class="md:focus:responsive-in-utilities"></div>
|
||||
<div class="md:focus:variants-in-utilities"></div>
|
||||
<div class="md:focus:both-in-utilities"></div>
|
||||
<div class="md:focus:responsive-at-root"></div>
|
||||
<div class="md:focus:variants-at-root"></div>
|
||||
<div class="md:focus:both-at-root"></div>
|
||||
<div class="md:focus:responsive-in-components"></div>
|
||||
<div class="md:focus:variants-in-components"></div>
|
||||
<div class="md:focus:both-in-components"></div>
|
||||
91
tests/jit/responsive-and-variants-atrules.test.js
Normal file
91
tests/jit/responsive-and-variants-atrules.test.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('responsive and variants atrules', () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './responsive-and-variants-atrules.test.html')],
|
||||
mode: 'jit',
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
@responsive {
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@responsive {
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@responsive {
|
||||
.responsive-in-components {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-components {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './responsive-and-variants-atrules.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
19
tests/jit/svelte-syntax.test.css
Normal file
19
tests/jit/svelte-syntax.test.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:bg-blue-500:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
33
tests/jit/svelte-syntax.test.js
Normal file
33
tests/jit/svelte-syntax.test.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('basic usage', () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './svelte-syntax.test.svelte')],
|
||||
mode: 'jit',
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './svelte-syntax.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
11
tests/jit/svelte-syntax.test.svelte
Normal file
11
tests/jit/svelte-syntax.test.svelte
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<script>
|
||||
let current = 'foo'
|
||||
</script>
|
||||
|
||||
<button class:lg:hover:bg-blue-500={current === 'foo'}>Click me</button>
|
||||
|
||||
<button
|
||||
class:bg-red-500={current === 'foo'}
|
||||
>
|
||||
Click me
|
||||
</button>
|
||||
225
tests/jit/variants.test.css
Normal file
225
tests/jit/variants.test.css
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
* {
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
}
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.first\:shadow-md:first-child {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.last\:shadow-md:last-child {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.odd\:shadow-md:nth-child(odd) {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.even\:shadow-md:nth-child(even) {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.visited\:shadow-md:visited {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.checked\:shadow-md:checked {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.focus-within\:shadow-md:focus-within {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.hover\:shadow-md:hover {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.focus\:shadow-md:focus {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.focus\:hover\:shadow-md:focus:hover {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.focus-visible\:shadow-md:focus-visible {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.active\:shadow-md:active {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.disabled\:shadow-md:disabled {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:first-child .group-first\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:last-child .group-last\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:nth-child(odd) .group-odd\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:nth-child(even) .group-even\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:visited .group-visited\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:checked .group-checked\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:focus-within .group-focus-within\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:hover .group-hover\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:focus .group-focus\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:focus-visible .group-focus-visible\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:active .group-active\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:disabled .group-disabled\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
[dir='ltr'] .ltr\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
[dir='rtl'] .rtl\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
.dark .dark\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.sm\:active\:shadow-md:active {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.group:focus .md\:group-focus\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.dark .lg\:dark\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.\32xl\:shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.dark .\32xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
tests/jit/variants.test.html
Normal file
68
tests/jit/variants.test.html
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Basic pseudo variants -->
|
||||
<div class="shadow-md"></div>
|
||||
<div class="hover:shadow-md"></div>
|
||||
<div class="focus:shadow-md"></div>
|
||||
<div class="disabled:shadow-md"></div>
|
||||
<div class="active:shadow-md"></div>
|
||||
<div class="first:shadow-md"></div>
|
||||
<div class="last:shadow-md"></div>
|
||||
<div class="even:shadow-md"></div>
|
||||
<div class="odd:shadow-md"></div>
|
||||
<div class="visited:shadow-md"></div>
|
||||
<div class="checked:shadow-md"></div>
|
||||
<div class="focus-within:shadow-md"></div>
|
||||
<div class="focus-visible:shadow-md"></div>
|
||||
|
||||
<!-- Group variants -->
|
||||
<div class="group-hover:shadow-md"></div>
|
||||
<div class="group-focus:shadow-md"></div>
|
||||
<div class="group-disabled:shadow-md"></div>
|
||||
<div class="group-active:shadow-md"></div>
|
||||
<div class="group-first:shadow-md"></div>
|
||||
<div class="group-last:shadow-md"></div>
|
||||
<div class="group-even:shadow-md"></div>
|
||||
<div class="group-odd:shadow-md"></div>
|
||||
<div class="group-odd:shadow-md"></div>
|
||||
<div class="group-visited:shadow-md"></div>
|
||||
<div class="group-checked:shadow-md"></div>
|
||||
<div class="group-focus-within:shadow-md"></div>
|
||||
<div class="group-focus-visible:shadow-md"></div>
|
||||
<div class="group-focus-visible:shadow-md"></div>
|
||||
|
||||
<!-- Reduced motion variants -->
|
||||
<div class="motion-safe:shadow-md"></div>
|
||||
<div class="motion-reduce:shadow-md"></div>
|
||||
|
||||
<!-- Direction variants -->
|
||||
<div class="rtl:shadow-md"></div>
|
||||
<div class="ltr:shadow-md"></div>
|
||||
|
||||
<!-- Dark mode variants -->
|
||||
<div class="dark:shadow-md"></div>
|
||||
|
||||
<!-- Screen variants -->
|
||||
<div class="sm:shadow-md"></div>
|
||||
<div class="md:shadow-md"></div>
|
||||
<div class="lg:shadow-md"></div>
|
||||
<div class="xl:shadow-md"></div>
|
||||
<div class="2xl:shadow-md"></div>
|
||||
|
||||
<!-- Stacked variants -->
|
||||
<div class="focus:hover:shadow-md"></div>
|
||||
<div class="sm:active:shadow-md"></div>
|
||||
<div class="md:group-focus:shadow-md"></div>
|
||||
<div class="lg:dark:shadow-md"></div>
|
||||
<div class="xl:dark:disabledshadow-md"></div>
|
||||
<div class="2xl:dark:motion-safe:focus-within:shadow-md"></div>
|
||||
</body>
|
||||
</html>
|
||||
34
tests/jit/variants.test.js
Normal file
34
tests/jit/variants.test.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import postcss from 'postcss'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
}
|
||||
|
||||
test('variants', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
mode: 'jit',
|
||||
purge: [path.resolve(__dirname, './variants.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
let expectedPath = path.resolve(__dirname, './variants.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
93
tests/layerAtRule.test.js
Normal file
93
tests/layerAtRule.test.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import postcss from 'postcss'
|
||||
import tailwind from '../src/index'
|
||||
|
||||
function run(input, config = {}) {
|
||||
return postcss([tailwind({ corePlugins: [], ...config })]).process(input, { from: undefined })
|
||||
}
|
||||
|
||||
test('layers are grouped and inserted at the matching @tailwind rule', () => {
|
||||
const input = `
|
||||
@layer vanilla {
|
||||
strong { font-weight: medium }
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn { background: blue }
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.align-banana { text-align: banana }
|
||||
}
|
||||
|
||||
@layer base {
|
||||
h1 { font-weight: bold }
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.card { border-radius: 12px }
|
||||
}
|
||||
|
||||
@layer base {
|
||||
p { font-weight: normal }
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.align-sandwich { text-align: sandwich }
|
||||
}
|
||||
|
||||
@layer chocolate {
|
||||
a { text-decoration: underline }
|
||||
}
|
||||
`
|
||||
|
||||
const expected = `
|
||||
@layer vanilla {
|
||||
strong { font-weight: medium }
|
||||
}
|
||||
|
||||
body { margin: 0 }
|
||||
h1 { font-weight: bold }
|
||||
p { font-weight: normal }
|
||||
|
||||
.input { background: white }
|
||||
.btn { background: blue }
|
||||
.card { border-radius: 12px }
|
||||
|
||||
.float-squirrel { float: squirrel }
|
||||
.align-banana { text-align: banana }
|
||||
.align-sandwich { text-align: sandwich }
|
||||
|
||||
@layer chocolate {
|
||||
a { text-decoration: underline }
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, {
|
||||
plugins: [
|
||||
function ({ addBase, addComponents, addUtilities }) {
|
||||
addBase({
|
||||
body: {
|
||||
margin: 0,
|
||||
},
|
||||
})
|
||||
|
||||
addComponents({
|
||||
'.input': { background: 'white' },
|
||||
})
|
||||
|
||||
addUtilities({
|
||||
'.float-squirrel': { float: 'squirrel' },
|
||||
})
|
||||
},
|
||||
],
|
||||
}).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
14
tests/negateValue.test.js
Normal file
14
tests/negateValue.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import negateValue from '../src/util/negateValue'
|
||||
|
||||
test('it negates numeric CSS values', () => {
|
||||
expect(negateValue('5')).toEqual('-5')
|
||||
expect(negateValue('10px')).toEqual('-10px')
|
||||
expect(negateValue('18rem')).toEqual('-18rem')
|
||||
expect(negateValue('-10')).toEqual('10')
|
||||
expect(negateValue('-7ch')).toEqual('7ch')
|
||||
})
|
||||
|
||||
test('it leaves keywords untouched', () => {
|
||||
expect(negateValue('auto')).toEqual('auto')
|
||||
expect(negateValue('cover')).toEqual('cover')
|
||||
})
|
||||
228
tests/parseAnimationValue.test.js
Normal file
228
tests/parseAnimationValue.test.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
import parseAnimationValue from '../src/util/parseAnimationValue'
|
||||
import { produce } from './util/produce'
|
||||
|
||||
describe('Tailwind Defaults', () => {
|
||||
it.each([
|
||||
[
|
||||
'spin 1s linear infinite',
|
||||
{ name: 'spin', duration: '1s', timingFunction: 'linear', iterationCount: 'infinite' },
|
||||
],
|
||||
[
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
{
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
[
|
||||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
{
|
||||
name: 'pulse',
|
||||
duration: '2s',
|
||||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
['bounce 1s infinite', { name: 'bounce', duration: '1s', iterationCount: 'infinite' }],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
expect(parseAnimationValue(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('MDN Examples', () => {
|
||||
it.each([
|
||||
[
|
||||
'3s ease-in 1s 2 reverse both paused slidein',
|
||||
{
|
||||
delay: '1s',
|
||||
direction: 'reverse',
|
||||
duration: '3s',
|
||||
fillMode: 'both',
|
||||
iterationCount: '2',
|
||||
name: 'slidein',
|
||||
playState: 'paused',
|
||||
timingFunction: 'ease-in',
|
||||
},
|
||||
],
|
||||
[
|
||||
'slidein 3s linear 1s',
|
||||
{ delay: '1s', duration: '3s', name: 'slidein', timingFunction: 'linear' },
|
||||
],
|
||||
['slidein 3s', { duration: '3s', name: 'slidein' }],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
expect(parseAnimationValue(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('duration & delay', () => {
|
||||
it.each([
|
||||
// Positive seconds (integer)
|
||||
['spin 1s 1s linear', { duration: '1s', delay: '1s' }],
|
||||
['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
|
||||
['spin 1s 2s linear', { duration: '1s', delay: '2s' }],
|
||||
|
||||
// Negative seconds (integer)
|
||||
['spin -1s -1s linear', { duration: '-1s', delay: '-1s' }],
|
||||
['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
|
||||
['spin -1s -2s linear', { duration: '-1s', delay: '-2s' }],
|
||||
|
||||
// Positive seconds (float)
|
||||
['spin 1.321s 1.321s linear', { duration: '1.321s', delay: '1.321s' }],
|
||||
['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
|
||||
['spin 1.321s 2.321s linear', { duration: '1.321s', delay: '2.321s' }],
|
||||
|
||||
// Negative seconds (float)
|
||||
['spin -1.321s -1.321s linear', { duration: '-1.321s', delay: '-1.321s' }],
|
||||
['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
|
||||
['spin -1.321s -2.321s linear', { duration: '-1.321s', delay: '-2.321s' }],
|
||||
|
||||
// Positive milliseconds (integer)
|
||||
['spin 100ms 100ms linear', { duration: '100ms', delay: '100ms' }],
|
||||
['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
|
||||
['spin 100ms 200ms linear', { duration: '100ms', delay: '200ms' }],
|
||||
|
||||
// Negative milliseconds (integer)
|
||||
['spin -100ms -100ms linear', { duration: '-100ms', delay: '-100ms' }],
|
||||
['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
|
||||
['spin -100ms -200ms linear', { duration: '-100ms', delay: '-200ms' }],
|
||||
|
||||
// Positive milliseconds (float)
|
||||
['spin 100.321ms 100.321ms linear', { duration: '100.321ms', delay: '100.321ms' }],
|
||||
['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
|
||||
['spin 100.321ms 200.321ms linear', { duration: '100.321ms', delay: '200.321ms' }],
|
||||
|
||||
// Negative milliseconds (float)
|
||||
['spin -100.321ms -100.321ms linear', { duration: '-100.321ms', delay: '-100.321ms' }],
|
||||
['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
|
||||
['spin -100.321ms -200.321ms linear', { duration: '-100.321ms', delay: '-200.321ms' }],
|
||||
])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed.duration).toEqual(duration)
|
||||
expect(parsed.delay).toEqual(delay)
|
||||
})
|
||||
})
|
||||
|
||||
describe('iteration count', () => {
|
||||
it.each([
|
||||
// Number
|
||||
['1 spin 200s 100s linear', '1'],
|
||||
['spin 2 200s 100s linear', '2'],
|
||||
['spin 200s 3 100s linear', '3'],
|
||||
['spin 200s 100s 4 linear', '4'],
|
||||
['spin 200s 100s linear 5', '5'],
|
||||
|
||||
// Infinite
|
||||
['infinite spin 200s 100s linear', 'infinite'],
|
||||
['spin infinite 200s 100s linear', 'infinite'],
|
||||
['spin 200s infinite 100s linear', 'infinite'],
|
||||
['spin 200s 100s infinite linear', 'infinite'],
|
||||
['spin 200s 100s linear infinite', 'infinite'],
|
||||
])(
|
||||
'should be possible to parse "%s" with an iteraction count of "%s"',
|
||||
(input, iterationCount) => {
|
||||
expect(parseAnimationValue(input).iterationCount).toEqual(iterationCount)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('iteration count', () => {
|
||||
it.each([
|
||||
// Number
|
||||
['1 spin 200s 100s linear', '1'],
|
||||
['spin 2 200s 100s linear', '2'],
|
||||
['spin 200s 3 100s linear', '3'],
|
||||
['spin 200s 100s 4 linear', '4'],
|
||||
['spin 200s 100s linear 5', '5'],
|
||||
['100 spin 200s 100s linear', '100'],
|
||||
['spin 200 200s 100s linear', '200'],
|
||||
['spin 200s 300 100s linear', '300'],
|
||||
['spin 200s 100s 400 linear', '400'],
|
||||
['spin 200s 100s linear 500', '500'],
|
||||
|
||||
// Infinite
|
||||
['infinite spin 200s 100s linear', 'infinite'],
|
||||
['spin infinite 200s 100s linear', 'infinite'],
|
||||
['spin 200s infinite 100s linear', 'infinite'],
|
||||
['spin 200s 100s infinite linear', 'infinite'],
|
||||
['spin 200s 100s linear infinite', 'infinite'],
|
||||
])(
|
||||
'should be possible to parse "%s" with an iteraction count of "%s"',
|
||||
(input, iterationCount) => {
|
||||
expect(parseAnimationValue(input).iterationCount).toEqual(iterationCount)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('multiple animations', () => {
|
||||
it('should be possible to parse multiple applications at once', () => {
|
||||
const input = [
|
||||
'spin 1s linear infinite',
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
].join(',')
|
||||
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(3)
|
||||
expect(parsed).toEqual([
|
||||
{ name: 'spin', duration: '1s', timingFunction: 'linear', iterationCount: 'infinite' },
|
||||
{
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
{
|
||||
name: 'pulse',
|
||||
duration: '2s',
|
||||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('randomized crazy big examples', () => {
|
||||
function reOrder(input, offset = 0) {
|
||||
return [...input.slice(offset), ...input.slice(0, offset)]
|
||||
}
|
||||
|
||||
it.each(
|
||||
produce((choose) => {
|
||||
const direction = choose('normal', 'reverse', 'alternate', 'alternate-reverse')
|
||||
const playState = choose('running', 'paused')
|
||||
const fillMode = choose('none', 'forwards', 'backwards', 'both')
|
||||
const iterationCount = choose('infinite', '1', '100')
|
||||
const timingFunction = choose(
|
||||
'linear',
|
||||
'ease',
|
||||
'ease-in',
|
||||
'ease-out',
|
||||
'ease-in-out',
|
||||
'cubic-bezier(0, 0, 0.2, 1)',
|
||||
'steps(4, end)'
|
||||
)
|
||||
const name = choose('animation-name-a', 'animation-name-b')
|
||||
const inputArgs = [direction, playState, fillMode, iterationCount, timingFunction, name]
|
||||
const orderOffset = choose(...Array(inputArgs.length).keys())
|
||||
|
||||
return [
|
||||
// Input
|
||||
reOrder(inputArgs, orderOffset).join(' '),
|
||||
|
||||
// Output
|
||||
{
|
||||
direction,
|
||||
playState,
|
||||
fillMode,
|
||||
iterationCount,
|
||||
timingFunction,
|
||||
name,
|
||||
},
|
||||
]
|
||||
})
|
||||
)('should be possible to parse "%s"', (input, output) => {
|
||||
expect(parseAnimationValue(input)).toEqual(output)
|
||||
})
|
||||
})
|
||||
302
tests/parseObjectStyles.test.js
Normal file
302
tests/parseObjectStyles.test.js
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
import parseObjectStyles from '../src/util/parseObjectStyles'
|
||||
import postcss from 'postcss'
|
||||
|
||||
function css(nodes) {
|
||||
return postcss.root({ nodes }).toString()
|
||||
}
|
||||
|
||||
test('it parses simple single class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foobar': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foobar {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses multiple class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
'.bar': {
|
||||
width: '200px',
|
||||
height: '100px',
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem
|
||||
}
|
||||
.bar {
|
||||
width: 200px;
|
||||
height: 100px
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested pseudo-selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
':hover': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
':focus': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo:hover {
|
||||
background-color: orange;
|
||||
}
|
||||
.foo:focus {
|
||||
background-color: blue;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses top-level media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'@media (min-width: 200px)': {
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@media (min-width: 200px)': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it bubbles nested screen rules', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@screen sm': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen sm {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses pseudo-selectors in nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
':hover': {
|
||||
'@media (min-width: 200px)': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo:hover {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses descendant selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo .bar {
|
||||
background-color: orange;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested multi-class selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'&.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo.bar {
|
||||
background-color: orange;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested multi-class selectors in media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@media (min-width: 200px)': {
|
||||
'&.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo.bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it strips empty selectors when nesting', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo .bar {
|
||||
background-color: orange
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it can parse an array of styles', () => {
|
||||
const result = parseObjectStyles([
|
||||
{
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
{
|
||||
'.bar': {
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
},
|
||||
{
|
||||
'.foo': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: orange
|
||||
}
|
||||
.bar {
|
||||
background-color: red
|
||||
}
|
||||
.foo {
|
||||
background-color: blue
|
||||
}
|
||||
`)
|
||||
})
|
||||
131
tests/plugins/animation.test.js
Normal file
131
tests/plugins/animation.test.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import postcss from 'postcss'
|
||||
import processPlugins from '../../src/util/processPlugins'
|
||||
import plugin from '../../src/plugins/animation'
|
||||
|
||||
function css(nodes) {
|
||||
return postcss.root({ nodes }).toString()
|
||||
}
|
||||
|
||||
test('defining animation and keyframes', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
animation: {
|
||||
none: 'none',
|
||||
spin: 'spin 1s linear infinite',
|
||||
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
},
|
||||
keyframes: {
|
||||
spin: { to: { transform: 'rotate(360deg)' } },
|
||||
ping: { '75%, 100%': { transform: 'scale(2)', opacity: '0' } },
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
animation: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = processPlugins([plugin()], config)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes ping {
|
||||
75%, 100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.animate-none { animation: none; }
|
||||
.animate-spin { animation: spin 1s linear infinite; }
|
||||
.animate-ping { animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; }
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('defining animation and keyframes with prefix', () => {
|
||||
const config = {
|
||||
prefix: 'tw-',
|
||||
theme: {
|
||||
animation: {
|
||||
none: 'none',
|
||||
spin: 'spin 1s linear infinite',
|
||||
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
},
|
||||
keyframes: {
|
||||
spin: { to: { transform: 'rotate(360deg)' } },
|
||||
ping: { '75%, 100%': { transform: 'scale(2)', opacity: '0' } },
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
animation: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = processPlugins([plugin()], config)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
@keyframes tw-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes tw-ping {
|
||||
75%, 100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.tw-animate-none { animation: none; }
|
||||
.tw-animate-spin { animation: tw-spin 1s linear infinite; }
|
||||
.tw-animate-ping { animation: tw-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; }
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('defining animation and keyframes with prefix (skip undefined animations)', () => {
|
||||
const config = {
|
||||
prefix: 'tw-',
|
||||
theme: {
|
||||
animation: {
|
||||
none: 'none',
|
||||
spin: 'spin 1s linear infinite',
|
||||
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
},
|
||||
keyframes: {
|
||||
spin: { to: { transform: 'rotate(360deg)' } },
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
animation: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = processPlugins([plugin()], config)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
@keyframes tw-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.tw-animate-none { animation: none; }
|
||||
.tw-animate-spin { animation: tw-spin 1s linear infinite; }
|
||||
.tw-animate-ping { animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; }
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
28
tests/plugins/backgroundColor.test.js
Normal file
28
tests/plugins/backgroundColor.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/backgroundColor'
|
||||
|
||||
test('defining color as a function', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
backgroundColor: {
|
||||
black: ({ opacityVariable: _ }) => 'black',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
backgroundColor: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.bg-black': {
|
||||
'background-color': 'black',
|
||||
},
|
||||
},
|
||||
[],
|
||||
],
|
||||
])
|
||||
})
|
||||
28
tests/plugins/borderColor.test.js
Normal file
28
tests/plugins/borderColor.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/borderColor'
|
||||
|
||||
test('defining color as a function', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
borderColor: {
|
||||
black: ({ opacityVariable: _ }) => 'black',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
borderColor: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.border-black': {
|
||||
'border-color': 'black',
|
||||
},
|
||||
},
|
||||
[],
|
||||
],
|
||||
])
|
||||
})
|
||||
79
tests/plugins/boxShadow.test.js
Normal file
79
tests/plugins/boxShadow.test.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/boxShadow'
|
||||
|
||||
test('box shadow can use DEFAULT keyword and negative prefix syntax', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
boxShadow: {
|
||||
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'-': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
||||
'-md': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
boxShadow: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, options) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
options,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'*': {
|
||||
'--tw-shadow': '0 0 #0000',
|
||||
},
|
||||
},
|
||||
options: { respectImportant: false },
|
||||
},
|
||||
{
|
||||
utilities: {
|
||||
'.shadow': {
|
||||
'--tw-shadow': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||
'box-shadow':
|
||||
'var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)',
|
||||
},
|
||||
'.shadow-md': {
|
||||
'--tw-shadow': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'box-shadow':
|
||||
'var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)',
|
||||
},
|
||||
'.-shadow': {
|
||||
'--tw-shadow': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
||||
'box-shadow':
|
||||
'var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)',
|
||||
},
|
||||
'.-shadow-md': {
|
||||
'--tw-shadow': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'box-shadow':
|
||||
'var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)',
|
||||
},
|
||||
},
|
||||
options: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
28
tests/plugins/divideColor.test.js
Normal file
28
tests/plugins/divideColor.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/divideColor'
|
||||
|
||||
test('defining color as a function', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
divideColor: {
|
||||
black: ({ opacityVariable: _ }) => 'black',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
divideColor: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.divide-black > :not([hidden]) ~ :not([hidden])': {
|
||||
'border-color': 'black',
|
||||
},
|
||||
},
|
||||
[],
|
||||
],
|
||||
])
|
||||
})
|
||||
74
tests/plugins/divideWidth.test.js
Normal file
74
tests/plugins/divideWidth.test.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/divideWidth'
|
||||
|
||||
test('generating divide width utilities', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
divideWidth: {
|
||||
DEFAULT: '1px',
|
||||
0: '0',
|
||||
2: '2px',
|
||||
4: '4px',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
divideWidth: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.divide-y > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(1px * calc(1 - var(--tw-divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(1px * var(--tw-divide-y-reverse))',
|
||||
},
|
||||
'.divide-x > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(1px * var(--tw-divide-x-reverse))',
|
||||
'border-left-width': 'calc(1px * calc(1 - var(--tw-divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-0 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(0px * calc(1 - var(--tw-divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(0px * var(--tw-divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-0 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(0px * var(--tw-divide-x-reverse))',
|
||||
'border-left-width': 'calc(0px * calc(1 - var(--tw-divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-2 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(2px * calc(1 - var(--tw-divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(2px * var(--tw-divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-2 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(2px * var(--tw-divide-x-reverse))',
|
||||
'border-left-width': 'calc(2px * calc(1 - var(--tw-divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-4 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(4px * calc(1 - var(--tw-divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(4px * var(--tw-divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-4 > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(4px * var(--tw-divide-x-reverse))',
|
||||
'border-left-width': 'calc(4px * calc(1 - var(--tw-divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-reverse > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-y-reverse': '1',
|
||||
},
|
||||
'.divide-x-reverse > :not([hidden]) ~ :not([hidden])': {
|
||||
'--tw-divide-x-reverse': '1',
|
||||
},
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
28
tests/plugins/fill.test.js
Normal file
28
tests/plugins/fill.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/fill'
|
||||
|
||||
test('defining color as a function', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fill: {
|
||||
black: ({ opacityVariable: _ }) => 'black',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fill: [],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.fill-black': {
|
||||
fill: 'black',
|
||||
},
|
||||
},
|
||||
[],
|
||||
],
|
||||
])
|
||||
})
|
||||
86
tests/plugins/fontSize.test.js
Normal file
86
tests/plugins/fontSize.test.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/fontSize'
|
||||
|
||||
test('font-size utilities can include a default line-height', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', '24px'],
|
||||
lg: ['20px', '28px'],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'line-height': '24px' },
|
||||
'.text-lg': { 'font-size': '20px', 'line-height': '28px' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default letter-spacing', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'letter-spacing': '-0.01em' },
|
||||
'.text-lg': { 'font-size': '20px', 'letter-spacing': '-0.02em' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default line-height and letter-spacing', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'line-height': '24px', 'letter-spacing': '-0.01em' },
|
||||
'.text-lg': { 'font-size': '20px', 'line-height': '28px', 'letter-spacing': '-0.02em' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
82
tests/plugins/gradientColorStops.test.js
Normal file
82
tests/plugins/gradientColorStops.test.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import postcss from 'postcss'
|
||||
import tailwind from '../../src/index'
|
||||
|
||||
test('opacity variables are given to colors defined as closures', () => {
|
||||
return postcss([
|
||||
tailwind({
|
||||
theme: {
|
||||
colors: {
|
||||
primary: ({ opacityVariable, opacityValue }) => {
|
||||
if (opacityValue !== undefined) {
|
||||
return `rgba(31,31,31,${opacityValue})`
|
||||
}
|
||||
|
||||
if (opacityVariable !== undefined) {
|
||||
return `rgba(31,31,31,var(${opacityVariable},1))`
|
||||
}
|
||||
|
||||
return `rgb(31,31,31)`
|
||||
},
|
||||
secondary: 'hsl(10, 50%, 50%)',
|
||||
},
|
||||
opacity: {
|
||||
50: '0.5',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
textColor: [],
|
||||
textOpacity: [],
|
||||
gradientColorStops: [],
|
||||
},
|
||||
corePlugins: ['textColor', 'textOpacity', 'gradientColorStops'],
|
||||
}),
|
||||
])
|
||||
.process('@tailwind utilities', { from: undefined })
|
||||
.then((result) => {
|
||||
const expected = `
|
||||
.text-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(31, 31, 31, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsla(10, 50%, 50%, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-opacity-50 {
|
||||
--tw-text-opacity: 0.5;
|
||||
}
|
||||
|
||||
.from-primary {
|
||||
--tw-gradient-from: rgb(31, 31, 31);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(31, 31, 31, 0));
|
||||
}
|
||||
|
||||
.from-secondary {
|
||||
--tw-gradient-from: hsl(10, 50%, 50%);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, hsla(10, 50%, 50%, 0));
|
||||
}
|
||||
|
||||
.via-primary {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), rgb(31, 31, 31),
|
||||
var(--tw-gradient-to, rgba(31, 31, 31, 0));
|
||||
}
|
||||
|
||||
.via-secondary {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), hsl(10, 50%, 50%),
|
||||
var(--tw-gradient-to, hsla(10, 50%, 50%, 0));
|
||||
}
|
||||
|
||||
.to-primary {
|
||||
--tw-gradient-to: rgb(31, 31, 31);
|
||||
}
|
||||
|
||||
.to-secondary {
|
||||
--tw-gradient-to: hsl(10, 50%, 50%);
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
53
tests/plugins/letterSpacing.test.js
Normal file
53
tests/plugins/letterSpacing.test.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/letterSpacing'
|
||||
|
||||
test('letter spacing can use negative prefix syntax', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
letterSpacing: {
|
||||
'-1': '-0.025em',
|
||||
1: '0.025em',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
letterSpacing: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: [
|
||||
{
|
||||
'.-tracking-1': { letterSpacing: '-0.025em' },
|
||||
'.tracking-1': { letterSpacing: '0.025em' },
|
||||
},
|
||||
],
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue