Skip to content

Commit 7565099

Browse files
authored
Integrations setup (#4354)
* add integration test tools * setup jest in the integrations folder * add `test:integrations` script The default `npm test` script will ignore all the tests in the `integrations` folder. * add integration tests with `webpack 5` * add integration tests with `postcss-cli` * add `npm run install:integrations` script This script will run `npm install` in every integration, and in the integrations folder itself (to setup Jest for example). * add `toIncludeCss` custom matcher * increate Jest timeout for integration tests * add integration tests with `vite` * add integration tests with `webpack 4` * add isomorphic fetch * add the ability to wait for specific stdout/stderr output * write vite tests, assert using API calls We will wait for the correct stdout/stderr output, once we know that we can request the fresh css, we will fetch it and make assertions accordingly. Port is currently hardcoded, maybe we should use a packaage to ensure that we use a free port. * add integration tests with `rollup` * add integration tests with `parcel` * run all integration tests in band * add .gitignore in integrations folder
1 parent 7d4f053 commit 7565099

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+64042
-1
lines changed

integrations/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

integrations/execute.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
let path = require('path')
2+
let { spawn } = require('child_process')
3+
let resolveToolRoot = require('./resolve-tool-root')
4+
5+
let runningProcessess = []
6+
7+
afterEach(() => {
8+
runningProcessess.splice(0).forEach((runningProcess) => runningProcess.stop())
9+
})
10+
11+
module.exports = function $(command, options = {}) {
12+
let abortController = new AbortController()
13+
let cwd = resolveToolRoot()
14+
15+
let args = command.split(' ')
16+
command = args.shift()
17+
command = path.resolve(cwd, 'node_modules', '.bin', command)
18+
19+
let stdoutMessages = []
20+
let stderrMessages = []
21+
22+
let stdoutActors = []
23+
let stderrActors = []
24+
25+
function notifyNext(actors, messages) {
26+
if (actors.length <= 0) return
27+
let [next] = actors
28+
29+
for (let [idx, message] of messages.entries()) {
30+
if (next.predicate(message)) {
31+
messages.splice(0, idx + 1)
32+
let actorIdx = actors.indexOf(next)
33+
actors.splice(actorIdx, 1)
34+
next.resolve()
35+
break
36+
}
37+
}
38+
}
39+
40+
function notifyNextStdoutActor() {
41+
return notifyNext(stdoutActors, stdoutMessages)
42+
}
43+
44+
function notifyNextStderrActor() {
45+
return notifyNext(stderrActors, stderrMessages)
46+
}
47+
48+
let runningProcess = new Promise((resolve, reject) => {
49+
let child = spawn(command, args, {
50+
...options,
51+
env: {
52+
...process.env,
53+
...options.env,
54+
},
55+
signal: abortController.signal,
56+
cwd,
57+
})
58+
59+
let stdout = ''
60+
let stderr = ''
61+
62+
child.stdout.on('data', (data) => {
63+
stdoutMessages.push(data.toString())
64+
notifyNextStdoutActor()
65+
stdout += data
66+
})
67+
68+
child.stderr.on('data', (data) => {
69+
stderrMessages.push(data.toString())
70+
notifyNextStderrActor()
71+
stderr += data
72+
})
73+
74+
child.on('close', (code, signal) => {
75+
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({ code, stdout, stderr })
76+
})
77+
})
78+
79+
runningProcessess.push(runningProcess)
80+
81+
return Object.assign(runningProcess, {
82+
stop() {
83+
abortController.abort()
84+
return runningProcess
85+
},
86+
onStdout(predicate) {
87+
return new Promise((resolve) => {
88+
stdoutActors.push({ predicate, resolve })
89+
notifyNextStdoutActor()
90+
})
91+
},
92+
onStderr(predicate) {
93+
return new Promise((resolve) => {
94+
stderrActors.push({ predicate, resolve })
95+
notifyNextStderrActor()
96+
})
97+
},
98+
})
99+
}

integrations/html.js

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

integrations/io.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
let { rm, existsSync } = require('fs')
2+
let path = require('path')
3+
let fs = require('fs/promises')
4+
5+
let chokidar = require('chokidar')
6+
7+
let resolveToolRoot = require('./resolve-tool-root')
8+
9+
module.exports = function ({
10+
/** Output directory, relative to the tool. */
11+
output = 'dist',
12+
13+
/** Input directory, relative to the tool. */
14+
input = 'src',
15+
16+
/** Whether or not you want to cleanup the output directory. */
17+
cleanup = true,
18+
} = {}) {
19+
let toolRoot = resolveToolRoot()
20+
let fileCache = {}
21+
22+
let absoluteOutputFolder = path.resolve(toolRoot, output)
23+
let absoluteInputFolder = path.resolve(toolRoot, input)
24+
25+
if (cleanup) {
26+
beforeAll((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
27+
afterEach((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
28+
}
29+
30+
// Restore all written files
31+
afterEach(async () => {
32+
await Promise.all(
33+
Object.entries(fileCache).map(([file, content]) => fs.writeFile(file, content, 'utf8'))
34+
)
35+
})
36+
37+
async function readdir(start, parent = []) {
38+
let files = await fs.readdir(start, { withFileTypes: true })
39+
let resolvedFiles = await Promise.all(
40+
files.map((file) => {
41+
if (file.isDirectory()) {
42+
return readdir(path.resolve(start, file.name), [...parent, file.name])
43+
}
44+
return parent.concat(file.name).join(path.sep)
45+
})
46+
)
47+
return resolvedFiles.flat(Infinity)
48+
}
49+
50+
async function resolveFile(fileOrRegex, directory) {
51+
if (fileOrRegex instanceof RegExp) {
52+
let files = await readdir(directory)
53+
if (files.length === 0) {
54+
throw new Error(`No files exists in "${directory}"`)
55+
}
56+
57+
let filtered = files.filter((file) => fileOrRegex.test(file))
58+
if (filtered.length === 0) {
59+
throw new Error(`Not a single file matched: ${fileOrRegex}`)
60+
} else if (filtered.length > 1) {
61+
throw new Error(`Multiple files matched: ${fileOrRegex}`)
62+
}
63+
64+
return filtered[0]
65+
}
66+
67+
return fileOrRegex
68+
}
69+
70+
return {
71+
async readOutputFile(file) {
72+
file = await resolveFile(file, absoluteOutputFolder)
73+
return fs.readFile(path.resolve(absoluteOutputFolder, file), 'utf8')
74+
},
75+
async appendToInputFile(file, contents) {
76+
let filePath = path.resolve(absoluteInputFolder, file)
77+
if (!fileCache[filePath]) {
78+
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
79+
}
80+
81+
return fs.appendFile(filePath, contents, 'utf8')
82+
},
83+
async writeInputFile(file, contents) {
84+
let filePath = path.resolve(absoluteInputFolder, file)
85+
if (!fileCache[filePath]) {
86+
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
87+
}
88+
89+
return fs.writeFile(path.resolve(absoluteInputFolder, file), contents, 'utf8')
90+
},
91+
async waitForOutputFileCreation(file) {
92+
if (file instanceof RegExp) {
93+
let r = file
94+
let watcher = chokidar.watch(absoluteOutputFolder)
95+
96+
return new Promise((resolve) => {
97+
watcher.on('add', (file) => {
98+
if (r.test(file)) {
99+
watcher.close()
100+
resolve()
101+
}
102+
})
103+
})
104+
} else {
105+
let filePath = path.resolve(absoluteOutputFolder, file)
106+
let watcher = chokidar.watch(filePath)
107+
108+
let watcherPromise = new Promise((resolve) => {
109+
watcher.once('add', () => {
110+
watcher.close()
111+
resolve()
112+
})
113+
})
114+
115+
if (existsSync(filePath)) {
116+
watcher.close()
117+
return Promise.resolve()
118+
}
119+
120+
return watcherPromise
121+
}
122+
},
123+
async waitForOutputFileChange(file, cb = () => {}) {
124+
file = await resolveFile(file, absoluteOutputFolder)
125+
126+
let filePath = path.resolve(absoluteOutputFolder, file)
127+
let watcher = chokidar.watch(filePath)
128+
129+
return new Promise((resolve) => {
130+
let chain = Promise.resolve()
131+
watcher.once('change', () => {
132+
watcher.close()
133+
chain.then(() => resolve())
134+
})
135+
chain.then(() => cb())
136+
})
137+
},
138+
}
139+
}

0 commit comments

Comments
 (0)