improve integration tests (#4572)

* remove unused file

using syntax.js instead

* add a way to remove files that are generated from the tests

Essentially setting the restore cache contents to `null` as a special
value.

* combine the stdout and stderr output

* add jest-diff instead of relying on it existing *somewhere*

This also came with a small API change.

* add `--runInBand` to the test script

* add dedicated cli tests for the new Tailwind CLI
This commit is contained in:
Robin Malfait 2021-06-07 19:50:53 +02:00 committed by GitHub
parent f1e31682c8
commit 63a67cb8ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 338 additions and 23 deletions

View File

@ -58,21 +58,29 @@ module.exports = function $(command, options = {}) {
let stdout = ''
let stderr = ''
let combined = ''
child.stdout.on('data', (data) => {
stdoutMessages.push(data.toString())
notifyNextStdoutActor()
stdout += data
combined += data
})
child.stderr.on('data', (data) => {
stderrMessages.push(data.toString())
notifyNextStderrActor()
stderr += data
combined += data
})
child.on('close', (code, signal) => {
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({ code, stdout, stderr })
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({
code,
stdout,
stderr,
combined,
})
})
})

View File

@ -1,4 +0,0 @@
// Small helper to allow for html highlighting / formatting in most editors.
module.exports = function html(templates) {
return templates.join('')
}

View File

@ -30,7 +30,15 @@ module.exports = function ({
// Restore all written files
afterEach(async () => {
await Promise.all(
Object.entries(fileCache).map(([file, content]) => fs.writeFile(file, content, 'utf8'))
Object.entries(fileCache).map(async ([file, content]) => {
try {
if (content === null) {
return await fs.unlink(file)
} else {
return await fs.writeFile(file, content, 'utf8')
}
} catch {}
})
)
})
@ -68,6 +76,21 @@ module.exports = function ({
}
return {
cleanupFile(file) {
let filePath = path.resolve(toolRoot, file)
fileCache[filePath] = null
},
async fileExists(file) {
let filePath = path.resolve(toolRoot, file)
return existsSync(filePath)
},
async removeFile(file) {
let filePath = path.resolve(toolRoot, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
}
await fs.unlink(filePath)
},
async readOutputFile(file) {
file = await resolveFile(file, absoluteOutputFolder)
return fs.readFile(path.resolve(absoluteOutputFolder, file), 'utf8')
@ -83,7 +106,15 @@ module.exports = function ({
async writeInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
try {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
} catch (err) {
if (err.code === 'ENOENT') {
fileCache[filePath] = null // Sentinel value to `delete` the file afterwards. This also means that we are writing to a `new` file inside the test.
} else {
throw err
}
}
}
return fs.writeFile(path.resolve(absoluteInputFolder, file), contents, 'utf8')

View File

@ -1,11 +1,10 @@
{
"name": "postcss-cli",
"name": "tailwindcss-cli",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "postcss-cli",
"version": "0.0.0"
}
}

View File

@ -4,7 +4,7 @@
"version": "0.0.0",
"scripts": {
"build": "NODE_ENV=production node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css",
"test": "jest"
"test": "jest --runInBand"
},
"jest": {
"displayName": "Tailwind CSS CLI",

View File

@ -0,0 +1,269 @@
let path = require('path')
let $ = require('../../execute')
let { css, html } = require('../../syntax')
let resolveToolRoot = require('../../resolve-tool-root')
let { readOutputFile, writeInputFile, cleanupFile, fileExists, removeFile } = require('../../io')({
output: 'dist',
input: 'src',
})
let EXECUTABLE = 'node ../../lib/cli.js'
describe('Build command', () => {
test('--output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></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).toIncludeCss(
css`
*,
::before,
::after {
--tw-border-opacity: 1;
border-color: rgba(229, 231, 235, var(--tw-border-opacity));
}
`
)
// 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)
})
test('--no-autoprefixer', async () => {
await writeInputFile('index.html', html`<div class="select-none"></div>`)
await $(`${EXECUTABLE} --output ./dist/main.css`)
let withAutoprefixer = await readOutputFile('main.css')
expect(withAutoprefixer).toIncludeCss(css`
.select-none {
-webkit-user-select: none;
user-select: none;
}
`)
await $(`${EXECUTABLE} --output ./dist/main.css --no-autoprefixer`)
let withoutAutoprefixer = await readOutputFile('main.css')
expect(withoutAutoprefixer).toIncludeCss(css`
.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(
{
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
fontWeight: {
bold: 'BOLD',
},
},
},
variants: {
extend: {},
},
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('--help', async () => {
let { combined } = await $(`${EXECUTABLE} --help`)
expect(combined).toMatchInlineSnapshot(`
"
tailwindcss v2.1.2
Usage:
tailwindcss build [options]
Options:
-i, --input Input file
-o, --output Output file
-w, --watch Watch for changes and rebuild as needed
--jit Build using JIT mode
--files Template files to scan for class names
--postcss Load custom PostCSS configuration
-m, --minify Minify the output
-c, --config Path to a custom config file
--no-autoprefixer Disable autoprefixer
-h, --help Display usage information
"
`)
})
})
describe('Init command', () => {
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('--jit', async () => {
cleanupFile('with-jit.config.js')
let { combined } = await $(`${EXECUTABLE} init with-jit.config.js --jit`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: with-jit.config.js
"
`)
expect(await readOutputFile('../with-jit.config.js')).toContain("mode: 'jit'")
})
test('--full, --jit', async () => {
cleanupFile('full-with-jit.config.js')
let { combined } = await $(`${EXECUTABLE} init full-with-jit.config.js --jit --full`)
expect(combined).toMatchInlineSnapshot(`
"
Created Tailwind CSS config file: full-with-jit.config.js
"
`)
expect(await readOutputFile('../full-with-jit.config.js')).toContain("mode: 'jit'")
})
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(combined).toMatchInlineSnapshot(`
"
tailwindcss v2.1.2
Usage:
tailwindcss init [options]
Options:
--jit Initialize for JIT mode
-f, --full Initialize a full \`tailwind.config.js\` file
-p, --postcss Initialize a \`postcss.config.js\` file
-h, --help Display usage information
"
`)
})
})

View File

@ -1,5 +1,5 @@
const prettier = require('prettier')
const diff = require('jest-diff').default
const { diff } = require('jest-diff')
function format(input) {
return prettier.format(input, {

35
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "tailwindcss",
"version": "2.1.2",
"license": "MIT",
"dependencies": {
@ -59,6 +58,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.2",
"jest-diff": "^27.0.2",
"postcss": "^8.3.0",
"postcss-cli": "^8.3.1",
"prettier": "^2.3.0",
@ -11761,7 +11761,8 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.2.1.tgz",
"integrity": "sha512-aDFi80aHQ3JM3symJ5iKU70lm151ugIGFCI0yRZGpyjgQSDS+Fbe93QwypC1tCEllQE8p0S7TUu20ih1b9IKLA==",
"dev": true
"dev": true,
"requires": {}
},
"@tootallnate/once": {
"version": "1.1.2",
@ -11917,7 +11918,8 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
"dev": true,
"requires": {}
},
"acorn-node": {
"version": "1.8.2",
@ -12903,7 +12905,8 @@
"cssnano-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz",
"integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ=="
"integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==",
"requires": {}
},
"csso": {
"version": "4.2.0",
@ -13369,7 +13372,8 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
"dev": true
"dev": true,
"requires": {}
},
"eslint-plugin-prettier": {
"version": "3.4.0",
@ -14998,7 +15002,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
"dev": true
"dev": true,
"requires": {}
},
"jest-regex-util": {
"version": "27.0.1",
@ -16201,22 +16206,26 @@
"postcss-discard-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz",
"integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg=="
"integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==",
"requires": {}
},
"postcss-discard-duplicates": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz",
"integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA=="
"integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==",
"requires": {}
},
"postcss-discard-empty": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz",
"integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw=="
"integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==",
"requires": {}
},
"postcss-discard-overridden": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz",
"integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q=="
"integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==",
"requires": {}
},
"postcss-js": {
"version": "3.0.3",
@ -16308,7 +16317,8 @@
"postcss-normalize-charset": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz",
"integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg=="
"integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==",
"requires": {}
},
"postcss-normalize-display-values": {
"version": "5.0.1",
@ -17867,7 +17877,8 @@
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"dev": true
"dev": true,
"requires": {}
},
"xml-name-validator": {
"version": "3.0.0",

View File

@ -56,6 +56,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.2",
"jest-diff": "^27.0.2",
"postcss": "^8.3.0",
"postcss-cli": "^8.3.1",
"prettier": "^2.3.0",