-
Notifications
You must be signed in to change notification settings - Fork 211
/
Copy pathindex.ts
149 lines (118 loc) · 3.84 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { onTestFinished, test, TestOptions } from 'vitest'
import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
import dedent from 'dedent'
export interface TestUtils {
/** The "cwd" for this test */
root: string
}
export interface StorageSymlink {
[IS_A_SYMLINK]: true
filepath: string
type: 'file' | 'dir' | undefined
}
export interface Storage {
/** A list of files and their content */
[filePath: string]: string | Uint8Array | StorageSymlink
}
export interface TestConfig<Extras extends {}> {
name: string
fs?: Storage
debug?: boolean
prepare?(utils: TestUtils): Promise<Extras>
handle(utils: TestUtils & Extras): void | Promise<void>
options?: TestOptions
}
export function defineTest<T>(config: TestConfig<T>) {
return test(config.name, config.options ?? {}, async ({ expect }) => {
let utils = await setup(config)
let extras = await config.prepare?.(utils)
await config.handle({
...utils,
...extras,
})
})
}
async function setup<T>(config: TestConfig<T>): Promise<TestUtils> {
let randomId = Math.random().toString(36).substring(7)
let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`)
let doneDir = path.resolve(process.cwd(), `../../.debug/${randomId}-done`)
await fs.mkdir(baseDir, { recursive: true })
if (config.fs) {
await prepareFileSystem(baseDir, config.fs)
await installDependencies(baseDir, config.fs)
}
onTestFinished(async (result) => {
// Once done, move all the files to a new location
try {
await fs.rename(baseDir, doneDir)
} catch {
// If it fails it doesn't really matter. It only fails on Windows and then
// only randomly so whatever
console.error('Failed to move test files to done directory')
}
if (result.state === 'fail') return
if (path.sep === '\\') return
if (config.debug) return
// Remove the directory on *nix systems. Recursive removal on Windows will
// randomly fail b/c its slow and buggy.
await fs.rm(doneDir, { recursive: true })
})
return {
root: baseDir,
}
}
const IS_A_SYMLINK = Symbol('is-a-symlink')
export function symlinkTo(filepath: string, type?: 'file' | 'dir'): StorageSymlink {
return {
[IS_A_SYMLINK]: true as const,
filepath,
type,
}
}
async function prepareFileSystem(base: string, storage: Storage) {
// Create a temporary directory to store the test files
await fs.mkdir(base, { recursive: true })
// Write the files to disk
for (let [filepath, content] of Object.entries(storage)) {
let fullPath = path.resolve(base, filepath)
await fs.mkdir(path.dirname(fullPath), { recursive: true })
if (typeof content === 'object' && IS_A_SYMLINK in content) {
let target = path.resolve(base, content.filepath)
let type: string = content.type
if (os.platform() === 'win32' && content.type === 'dir') {
type = 'junction'
}
await fs.symlink(target, fullPath, type)
continue
}
await fs.writeFile(fullPath, content, { encoding: 'utf-8' })
}
}
async function installDependencies(base: string, storage: Storage) {
for (let filepath of Object.keys(storage)) {
if (!filepath.endsWith('package.json')) continue
let pkgDir = path.dirname(filepath)
let basePath = path.resolve(pkgDir, base)
await installDependenciesIn(basePath)
}
}
async function installDependenciesIn(dir: string) {
console.log(`Installing dependencies in ${dir}`)
await new Promise((resolve, reject) => {
proc.exec('npm install --package-lock=false', { cwd: dir }, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
}
export const css = dedent
export const scss = dedent
export const html = dedent
export const js = dedent
export const json = dedent