|
| 1 | +import { afterAll, onTestFinished, test, TestOptions } from 'vitest' |
| 2 | +import * as fs from 'node:fs/promises' |
| 3 | +import * as path from 'node:path' |
| 4 | +import * as proc from 'node:child_process' |
| 5 | +import dedent from 'dedent' |
| 6 | + |
| 7 | +export interface TestUtils { |
| 8 | + /** The "cwd" for this test */ |
| 9 | + root: string |
| 10 | +} |
| 11 | + |
| 12 | +export interface Storage { |
| 13 | + /** A list of files and their content */ |
| 14 | + [filePath: string]: string | Uint8Array |
| 15 | +} |
| 16 | + |
| 17 | +export interface TestConfig<Extras extends {}> { |
| 18 | + name: string |
| 19 | + fs: Storage |
| 20 | + prepare?(utils: TestUtils): Promise<Extras> |
| 21 | + handle(utils: TestUtils & Extras): void | Promise<void> |
| 22 | + |
| 23 | + options?: TestOptions |
| 24 | +} |
| 25 | + |
| 26 | +export function defineTest<T>(config: TestConfig<T>) { |
| 27 | + return test(config.name, config.options ?? {}, async ({ expect }) => { |
| 28 | + let utils = await setup(config) |
| 29 | + let extras = await config.prepare?.(utils) |
| 30 | + |
| 31 | + await config.handle({ |
| 32 | + ...utils, |
| 33 | + ...extras, |
| 34 | + }) |
| 35 | + }) |
| 36 | +} |
| 37 | + |
| 38 | +async function setup<T>(config: TestConfig<T>): Promise<TestUtils> { |
| 39 | + let randomId = Math.random().toString(36).substring(7) |
| 40 | + |
| 41 | + let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`) |
| 42 | + let doneDir = path.resolve(process.cwd(), `../../.debug/${randomId}-done`) |
| 43 | + |
| 44 | + await fs.mkdir(baseDir, { recursive: true }) |
| 45 | + |
| 46 | + await prepareFileSystem(baseDir, config.fs) |
| 47 | + await installDependencies(baseDir, config.fs) |
| 48 | + |
| 49 | + onTestFinished(async (result) => { |
| 50 | + // Once done, move all the files to a new location |
| 51 | + await fs.rename(baseDir, doneDir) |
| 52 | + |
| 53 | + if (result.state === 'fail') return |
| 54 | + |
| 55 | + if (path.sep === '\\') return |
| 56 | + |
| 57 | + // Remove the directory on *nix systems. Recursive removal on Windows will |
| 58 | + // randomly fail b/c its slow and buggy. |
| 59 | + await fs.rm(doneDir, { recursive: true }) |
| 60 | + }) |
| 61 | + |
| 62 | + return { |
| 63 | + root: baseDir, |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +async function prepareFileSystem(base: string, storage: Storage) { |
| 68 | + // Create a temporary directory to store the test files |
| 69 | + await fs.mkdir(base, { recursive: true }) |
| 70 | + |
| 71 | + // Write the files to disk |
| 72 | + for (let [filepath, content] of Object.entries(storage)) { |
| 73 | + let fullPath = path.resolve(base, filepath) |
| 74 | + await fs.mkdir(path.dirname(fullPath), { recursive: true }) |
| 75 | + await fs.writeFile(fullPath, content, { encoding: 'utf-8' }) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +async function installDependencies(base: string, storage: Storage) { |
| 80 | + for (let filepath of Object.keys(storage)) { |
| 81 | + if (!filepath.endsWith('package.json')) continue |
| 82 | + |
| 83 | + let pkgDir = path.dirname(filepath) |
| 84 | + let basePath = path.resolve(pkgDir, base) |
| 85 | + |
| 86 | + await installDependenciesIn(basePath) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +async function installDependenciesIn(dir: string) { |
| 91 | + console.log(`Installing dependencies in ${dir}`) |
| 92 | + |
| 93 | + await new Promise((resolve, reject) => { |
| 94 | + proc.exec('npm install --package-lock=false', { cwd: dir }, (err, res) => { |
| 95 | + if (err) { |
| 96 | + reject(err) |
| 97 | + } else { |
| 98 | + resolve(res) |
| 99 | + } |
| 100 | + }) |
| 101 | + }) |
| 102 | +} |
| 103 | + |
| 104 | +export const css = dedent |
| 105 | +export const html = dedent |
| 106 | +export const js = dedent |
| 107 | +export const json = dedent |
0 commit comments