vite/playground/test-utils.ts

182 lines
5.1 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/triple-slash-reference */
2020-12-20 03:33:13 +00:00
// test utils used in e2e tests for playgrounds.
2022-05-11 09:03:19 +00:00
// `import { getColor } from '~utils'`
2020-12-20 03:33:13 +00:00
2022-05-11 09:20:50 +00:00
// TODO: explicitly import APIs and remove this
/// <reference types="vitest/globals"/>
2020-12-20 03:33:13 +00:00
import fs from 'fs'
import path from 'path'
import colors from 'css-color-names'
2021-12-19 11:35:25 +00:00
import type { ElementHandle } from 'playwright-chromium'
2022-03-30 06:56:20 +00:00
import type { Manifest } from 'vite'
import { normalizePath } from 'vite'
2022-03-28 12:41:00 +00:00
import { fromComment } from 'convert-source-map'
2022-05-11 06:33:20 +00:00
import { expect } from 'vitest'
import type { ExecaChildProcess } from 'execa'
import { isBuild, isWindows, page, testDir } from './vitestSetup'
2022-05-11 09:03:19 +00:00
export * from './vitestSetup'
2022-05-11 09:03:19 +00:00
2022-04-04 18:33:48 +00:00
// make sure these ports are unique
export const ports = {
cli: 9510,
'cli-module': 9511,
'legacy/ssr': 9520,
lib: 9521,
'optimize-missing-deps': 9522,
'ssr-deps': 9600,
'ssr-html': 9601,
'ssr-pug': 9602,
'ssr-react': 9603,
'ssr-vue': 9604,
'ssr-webworker': 9605,
'css/postcss-caching': 5005,
'css/postcss-plugins-different-dir': 5006
}
2020-12-20 03:33:13 +00:00
const hexToNameMap: Record<string, string> = {}
Object.keys(colors).forEach((color) => {
hexToNameMap[colors[color]] = color
})
function componentToHex(c: number): string {
const hex = c.toString(16)
2021-07-13 15:05:08 +00:00
return hex.length === 1 ? '0' + hex : hex
2020-12-20 03:33:13 +00:00
}
function rgbToHex(rgb: string): string {
const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/)
if (match) {
const [_, rs, gs, bs] = match
return (
'#' +
componentToHex(parseInt(rs, 10)) +
componentToHex(parseInt(gs, 10)) +
componentToHex(parseInt(bs, 10))
)
} else {
return '#000000'
}
2020-12-20 03:33:13 +00:00
}
const timeout = (n: number) => new Promise((r) => setTimeout(r, n))
async function toEl(el: string | ElementHandle): Promise<ElementHandle> {
if (typeof el === 'string') {
return await page.$(el)
}
return el
}
export async function getColor(el: string | ElementHandle): Promise<string> {
2020-12-20 03:33:13 +00:00
el = await toEl(el)
const rgb = await el.evaluate((el) => getComputedStyle(el as Element).color)
return hexToNameMap[rgbToHex(rgb)] ?? rgb
2020-12-20 03:33:13 +00:00
}
export async function getBg(el: string | ElementHandle): Promise<string> {
2020-12-21 23:37:04 +00:00
el = await toEl(el)
return el.evaluate((el) => getComputedStyle(el as Element).backgroundImage)
}
export async function getBgColor(el: string | ElementHandle): Promise<string> {
el = await toEl(el)
return el.evaluate((el) => getComputedStyle(el as Element).backgroundColor)
}
export function readFile(filename: string): string {
return fs.readFileSync(path.resolve(testDir, filename), 'utf-8')
}
export function editFile(
filename: string,
replacer: (str: string) => string,
runInBuild: boolean = false
): void {
if (isBuild && !runInBuild) return
filename = path.resolve(testDir, filename)
2020-12-20 03:33:13 +00:00
const content = fs.readFileSync(filename, 'utf-8')
const modified = replacer(content)
fs.writeFileSync(filename, modified)
}
export function addFile(filename: string, content: string): void {
fs.writeFileSync(path.resolve(testDir, filename), content)
2021-01-20 22:45:41 +00:00
}
export function removeFile(filename: string): void {
fs.unlinkSync(path.resolve(testDir, filename))
2021-01-20 22:45:41 +00:00
}
export function listAssets(base = ''): string[] {
const assetsDir = path.join(testDir, 'dist', base, 'assets')
return fs.readdirSync(assetsDir)
}
export function findAssetFile(match: string | RegExp, base = ''): string {
const assetsDir = path.join(testDir, 'dist', base, 'assets')
2020-12-30 23:47:35 +00:00
const files = fs.readdirSync(assetsDir)
const file = files.find((file) => {
return file.match(match)
})
return file ? fs.readFileSync(path.resolve(assetsDir, file), 'utf-8') : ''
}
export function readManifest(base = ''): Manifest {
return JSON.parse(
fs.readFileSync(path.join(testDir, 'dist', base, 'manifest.json'), 'utf-8')
)
}
2020-12-20 03:33:13 +00:00
/**
* Poll a getter until the value it returns includes the expected value.
*/
export async function untilUpdated(
2020-12-23 04:07:57 +00:00
poll: () => string | Promise<string>,
2021-03-26 21:54:28 +00:00
expected: string,
runInBuild = false
): Promise<void> {
2021-03-26 21:54:28 +00:00
if (isBuild && !runInBuild) return
2021-02-03 03:59:25 +00:00
const maxTries = process.env.CI ? 100 : 50
2020-12-20 03:33:13 +00:00
for (let tries = 0; tries < maxTries; tries++) {
const actual = (await poll()) ?? ''
2020-12-20 03:33:13 +00:00
if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
expect(actual).toMatch(expected)
break
} else {
await timeout(50)
}
}
}
2022-03-28 12:41:00 +00:00
export const extractSourcemap = (content: string) => {
const lines = content.trim().split('\n')
return fromComment(lines[lines.length - 1]).toObject()
}
export const formatSourcemapForSnapshot = (map: any) => {
const root = normalizePath(testDir)
2022-03-28 12:41:00 +00:00
const m = { ...map }
delete m.file
delete m.names
m.sources = m.sources.map((source) => source.replace(root, '/root'))
return m
}
// helper function to kill process, uses taskkill on windows to ensure child process is killed too
export async function killProcess(
serverProcess: ExecaChildProcess
): Promise<void> {
if (isWindows) {
try {
const { default: execa } = await import('execa')
execa.commandSync(`taskkill /pid ${serverProcess.pid} /T /F`)
} catch (e) {
console.error('failed to taskkill:', e)
}
} else {
serverProcess.kill('SIGTERM', { forceKillAfterTimeout: 2000 })
}
}