tailwindcss/integrations/tailwindcss-cli/tests/cli.test.js
Robin Malfait e572dc645a
Bump lightningcss dependency (#11388)
* bump `lightningcss`

* allow for non-standard selector combinators such as `::deep`

* just use Prettier when comparing CSS results

We were using 2 different engines where the stable one was not using
Lightning CSS and the Oxide one was using Lightning CSS.

To ensure that we didn't have to rewrite every single test expectation,
the `toMatchFormattedCss` parsed both the actual and expected value
using Lightning CSS (to make the result similar), then it used Prettier
to make it... pretty.

Right now we _only_ use Lightning CSS, which means that we can drop the
additional lightningcss format step and just use Prettier on both the
actual and expected values.

Pretty also only prettifies the CSS, it doesn't rewrite it. E.g.:
`@media (min-width: 768px)` will not be optimized to `@media (width >=
768px)`, that's something that Lightning CSS does for us.

This will require some changes in our test output, but it will be
consistent afterwards because there won't be hidden transformation steps
anymore. Because up until now it could be that the actual result was
`color: black` but the tests showed `color: #000` (because it is
shorter).

This change will reflect reality.

* update tests based on previous commit

* only use `toMatchFormattedCss` instead of `toMatchCss`

They both do the exact same thing right now. While `toMatchCss` is
shorter, `toMatchFormattedCss` makes a bit more sense since we are
comparing the Prettier results.

* update integration tests
2023-06-07 15:56:32 +02:00

653 lines
18 KiB
JavaScript

let path = require('path')
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let resolveToolRoot = require('../../resolve-tool-root')
let version = require('../../../package.json').version
let {
cleanupFile,
fileExists,
readOutputFile,
removeFile,
waitForOutputFileCreation,
writeInputFile,
} = require('../../io')({
output: 'dist',
input: 'src',
})
let EXECUTABLE = 'node ../../lib/cli.js'
function dedent(input) {
let lines = input.split('\n')
let minIndent = lines.reduce(
(min, line) => Math.min(min, line.trim() === '' ? Infinity : line.match(/^\s*/)[0].length),
Infinity
)
return lines
.map((line) => line.slice(minIndent))
.join('\n')
.trim()
}
describe('Build command', () => {
test('--output', async () => {
await writeInputFile('index.html', html`<div class="font-bold shadow"></div>`)
await $(`${EXECUTABLE} --output ./dist/main.css`)
let contents = await readOutputFile('main.css')
// `-i` is omitted, therefore the default `@tailwind base; @tailwind
// components; @tailwind utilities` is used. However `preflight` is
// disabled. I still want to verify that the `base` got included.
expect(contents).toContain('--tw-ring-offset-shadow: 0 0 #0000')
expect(contents).toContain('--tw-ring-shadow: 0 0 #0000')
expect(contents).toContain('--tw-shadow: 0 0 #0000')
// Verify `utilities` output is correct
expect(contents).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
test('--input, --output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $(`${EXECUTABLE} --input ./src/index.css --output ./dist/main.css`)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
test('--minify', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $(`${EXECUTABLE} --output ./dist/main.css --minify`)
let withMinify = await readOutputFile('main.css')
// Verify that we got the expected output. Note: `.toIncludeCss` formats
// `actual` & `expected`
expect(withMinify).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await $(`${EXECUTABLE} --output ./dist/main.css`)
let withoutMinify = await readOutputFile('main.css')
// Let's verify that the actual minified output is smaller than the not
// minified version.
expect(withoutMinify.length).toBeGreaterThan(withMinify.length)
})
// Deprecated
test('--no-autoprefixer (should produce a warning)', async () => {
await writeInputFile('index.html', html`<div class="select-none"></div>`)
await writeInputFile(
'index.css',
css`
@tailwind utilities;
`
)
let runningProcess = $(
`${EXECUTABLE} --input ./src/index.css --output ./dist/main.css --no-autoprefixer`
)
let warning = runningProcess.onStderr((message) => message.includes('--no-autoprefixer'))
await runningProcess
expect(await warning).toMatchInlineSnapshot(`
"[deprecation] The --no-autoprefixer flag is deprecated and has no effect.
"
`)
let withoutAutoprefixer = await readOutputFile('main.css')
// This contains --webkit-user-select which may be strange, but it is expected because we are
// not handling the `--no-autoprefixer` flag anymore at all.
expect(withoutAutoprefixer).toMatchInlineSnapshot(`
".select-none {
-webkit-user-select: none;
user-select: none;
}
"
`)
})
test('--config (non-existing config file)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let { stderr } = await $(
`${EXECUTABLE} --output ./dist/main.css --config ./non-existing.config.js`
).catch((err) => err)
let toolRoot = resolveToolRoot()
expect(stderr).toEqual(
`Specified config file ${path.resolve(toolRoot, 'non-existing.config.js')} does not exist.\n`
)
})
test('--config (existing config file)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = `module.exports = ${JSON.stringify(
{
content: ['./src/index.html'],
theme: {
extend: {
fontWeight: {
bold: 'bold',
},
},
},
corePlugins: {
preflight: false,
},
plugins: [],
},
null,
2
)}`
await writeInputFile('../custom.config.js', customConfig)
await $(`${EXECUTABLE} --output ./dist/main.css --config ./custom.config.js`)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
`
)
})
test('--content', async () => {
await writeInputFile('other.html', html`<div class="font-bold"></div>`)
await $(`${EXECUTABLE} --content ./src/other.html --output ./dist/main.css`)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
test('--postcss (postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
plugins: [
function before(root, result) {
// Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply flex px-2 py-1 }')
root.append(btn.nodes)
},
function tailwindcss() {
return require(path.resolve('..', '..'))
},
function after(root, result) {
// Add '-after' to all the selectors
root.walkRules(rule => {
if (!rule.selector.startsWith('.')) return
rule.selector = rule.selector + '-after'
})
},
],
}
`
await writeInputFile('../postcss.config.js', customConfig)
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
padding: 0.25rem 0.5rem;
display: flex;
}
`
)
})
test('--postcss (custom.postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
plugins: [
function before(root, result) {
// Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply flex px-2 py-1 }')
root.append(btn.nodes)
},
function tailwindcss() {
return require(path.resolve('..', '..'))
},
function after(root, result) {
// Add '-after' to all the selectors
root.walkRules(rule => {
if (!rule.selector.startsWith('.')) return
rule.selector = rule.selector + '-after'
})
},
],
}
`
await writeInputFile('../custom.postcss.config.js', customConfig)
await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
padding: 0.25rem 0.5rem;
display: flex;
}
`
)
})
test('--postcss supports process options', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
map: { inline: true },
plugins: [
function tailwindcss() {
return require(path.resolve('..', '..'))
},
],
}
`
await writeInputFile('../postcss.config.js', customConfig)
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
let contents = await readOutputFile('main.css')
expect(contents).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
expect(contents).toContain(`/*# sourceMappingURL`)
})
test('--postcss supports process options with custom config', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
map: { inline: true },
plugins: [
function tailwindcss() {
return require(path.resolve('..', '..'))
},
],
}
`
await writeInputFile('../custom.postcss.config.js', customConfig)
await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`)
let contents = await readOutputFile('main.css')
expect(contents).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
expect(contents).toContain(`/*# sourceMappingURL`)
})
test('postcss-import is supported by default', async () => {
cleanupFile('src/test.css')
await writeInputFile('index.html', html`<div class="md:something-cool"></div>`)
await writeInputFile(
'test.css',
css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import './imported.css';
`
)
await $(
`${EXECUTABLE} --input ./src/test.css --content ./src/index.html --output ./dist/main.css`
)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
@media (min-width: 768px) {
.md\:something-cool {
color: red;
}
}
`
)
})
test('postcss-import is supported by default in watch mode', async () => {
cleanupFile('src/test.css')
await writeInputFile('index.html', html`<div class="md:something-cool"></div>`)
await writeInputFile(
'test.css',
css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import './imported.css';
`
)
let runningProcess = $(
`${EXECUTABLE} --watch --input ./src/test.css --content ./src/index.html --output ./dist/main.css`
)
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
@media (min-width: 768px) {
.md\:something-cool {
color: red;
}
}
`
)
await writeInputFile(
'imported.css',
css`
@layer utilities {
.something-cool {
color: blue;
}
}
`
)
await runningProcess.onStderr(function ready(message) {
return message.includes('Done in')
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
@media (min-width: 768px) {
.md\:something-cool {
color: #00f;
}
}
`
)
return runningProcess.stop()
})
test('postcss-import is included when using a custom postcss configuration', async () => {
cleanupFile('src/test.css')
await writeInputFile('index.html', html`<div class="md:something-cool"></div>`)
await writeInputFile(
'imported.css',
css`
.foo {
color: white;
}
`
)
await writeInputFile(
'test.css',
css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import './imported.css';
`
)
await $(
`${EXECUTABLE} --input ./src/test.css --content ./src/index.html --output ./dist/main.css --postcss`
)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.foo {
color: #fff;
}
`
)
})
test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} --help`)
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
tailwindcss build [options]
Options:
-i, --input Input file
-o, --output Output file
-w, --watch Watch for changes and rebuild as needed
-p, --poll Use polling instead of filesystem events when watching
--content Content paths to use for removing unused classes
--postcss Load custom PostCSS configuration
-m, --minify Minify the output
-c, --config Path to a custom config file
-h, --help Display usage information
`)
)
})
})
describe('Init command', () => {
it.each([
{ flags: [], name: 'tailwind.config.js' },
{ flags: ['--ts'], name: 'tailwind.config.ts' },
{ flags: ['--esm'], name: 'tailwind.config.js' },
{ flags: ['--full'], name: 'tailwind.config.js' },
{ flags: ['--ts', '--full'], name: 'tailwind.config.ts' },
{ flags: ['--esm', '--full'], name: 'tailwind.config.js' },
])('works with all these flags: %j', async ({ flags, name }) => {
await removeFile(name)
let { combined } = await $(`${EXECUTABLE} init ${flags.join(' ')}`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: ${name}
"
`)
expect(await fileExists(name)).toBe(true)
let content = await readOutputFile(`../${name}`)
if (flags.includes('--ts') || flags.includes('--esm')) {
expect(content).toContain('export default')
expect(content).not.toContain('module.exports =')
} else {
expect(content).toContain('module.exports =')
expect(content).not.toContain('export default')
}
if (flags.includes('--ts')) {
expect(content).toContain('satisfies Config')
}
if (flags.includes('--full')) {
expect(content.split('\n').length).toBeGreaterThan(50)
}
})
test('--full', async () => {
cleanupFile('full.config.js')
let { combined } = await $(`${EXECUTABLE} init full.config.js --full`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: full.config.js
"
`)
// Not a clean way to test this. We could require the file and verify that
// multiple keys in `theme` exists. However it loads `tailwindcss/colors`
// which doesn't exists in this context.
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
})
test('--postcss', async () => {
expect(await fileExists('postcss.config.js')).toBe(true)
await removeFile('postcss.config.js')
expect(await fileExists('postcss.config.js')).toBe(false)
let { combined } = await $(`${EXECUTABLE} init --postcss`)
expect(await fileExists('postcss.config.js')).toBe(true)
expect(combined).toMatchInlineSnapshot(`
"
tailwind.config.js already exists.
Created PostCSS config file: postcss.config.js
"
`)
})
test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} init --help`)
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
tailwindcss init [options]
Options:
--esm Initialize configuration file as ESM
--ts Initialize configuration file as TypeScript
-p, --postcss Initialize a \`postcss.config.js\` file
-f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information
`)
)
})
test('ESM config is created by default in an ESM project', async () => {
await removeFile('tailwind.config.js')
let pkg = await readOutputFile('../package.json')
await writeInputFile(
'../package.json',
JSON.stringify({
...JSON.parse(pkg),
type: 'module',
})
)
let { combined } = await $(`${EXECUTABLE} init`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: tailwind.config.js
"
`)
expect(await fileExists('./tailwind.config.js')).toBe(true)
// Not a clean way to test this.
expect(await readOutputFile('../tailwind.config.js')).toContain('export default')
await writeInputFile('../package.json', pkg)
})
test('CJS config is created by default in a non-ESM project', async () => {
await removeFile('tailwind.config.js')
let pkg = await readOutputFile('../package.json')
await writeInputFile(
'../package.json',
JSON.stringify({
...JSON.parse(pkg),
})
)
let { combined } = await $(`${EXECUTABLE} init`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: tailwind.config.js
"
`)
expect(await fileExists('./tailwind.config.js')).toBe(true)
// Not a clean way to test this.
expect(await readOutputFile('../tailwind.config.js')).toContain('module.exports')
await writeInputFile('../package.json', pkg)
})
})