Skip to content

Commit

Permalink
chore: rewrite tests (#1081)
Browse files Browse the repository at this point in the history
* remove prop tracking
* import cjs modules per interop
* remove dependency on jest environment
* add test environments and scripts
* disable coverage threshold
  • Loading branch information
ph-fritsche committed Dec 27, 2022
1 parent 1aa2027 commit e93a5af
Show file tree
Hide file tree
Showing 69 changed files with 1,028 additions and 367 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/coverage
/dist
/node_modules
/testenv
/scripts
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,11 @@ module.exports = {
'local-rules/explicit-globals': 'error',
},
},
{
files: ['**.{ts,tsx}'],
rules: {
'@typescript-eslint/no-unsafe-argument': 1,
},
},
],
}
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ jobs:
run: npm run lint
- name: 🧪 Test
run: npm run test -- --coverage
- name: 🚧 Build test environments
run: npm run setup:env
- name: 🔬 Test with toolbox
run: npm run test:toolbox
- name: 🏗 Build
run: npm run build

Expand Down
9 changes: 7 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ config.moduleNameMapper = {
config.testEnvironment = 'jsdom'

config.setupFilesAfterEnv = [
'<rootDir>/tests/_setup-env.js',
'<rootDir>/tests/react/_env/setup-env.js',
'<rootDir>/testenv/jest.js',
]

config.testMatch.push('<rootDir>/tests/**/*.+(js|jsx|ts|tsx)')
Expand All @@ -24,4 +23,10 @@ config.testPathIgnorePatterns.push('/_.*(?<!\\.test\\.[jt]sx?)$')
// Ignore declaration files
config.testPathIgnorePatterns.push('\\.d\\.ts$')

config.snapshotSerializers = [
require.resolve('jest-snapshot-serializer-raw/always'),
]

config.coverageThreshold = undefined

module.exports = config
33 changes: 22 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,45 @@
"build": "scripts ts-build2 --cjs --target es2019",
"lint": "kcd-scripts lint",
"setup": "npm install && npm run validate -s",
"setup:env": "node scripts/setup.js",
"test": "kcd-scripts test",
"test:jest": "kcd-scripts test",
"test:toolbox": "NODE_OPTIONS='--experimental-vm-modules --experimental-modules --experimental-import-meta-resolve' node scripts/test.js",
"test:debug": "kcd-scripts --inspect-brk test --runInBand",
"test:update": "npm test -- --updateSnapshot --coverage",
"validate": "kcd-scripts typecheck"
},
"devDependencies": {
"@ph.fritsche/scripts-config": "^2.4.0",
"@testing-library/dom": "^8.11.4",
"@ph.fritsche/toolbox": "^1.0.0-alpha.1",
"@testing-library/dom": "^8.19.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^13.0.0",
"@testing-library/react": "^13.4.0",
"@types/jest-in-case": "^1.0.3",
"@types/react": "^17.0.42",
"eslint-import-resolver-typescript": "^2.7.0",
"eslint-plugin-local-rules": "^1.1.0",
"@types/react": "^18.0.25",
"@types/sinonjs__fake-timers": "^8.1.2",
"css.escape": "^1.5.1",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-local-rules": "^1.3.2",
"expect": "^28.1.3",
"is-ci": "^3.0.1",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.0",
"istanbul-lib-source-maps": "^4.0.1",
"istanbul-reports": "^3.1.5",
"jest-in-case": "^1.0.2",
"jest-mock": "^28.1.3",
"jest-serializer-ansi": "^1.0.3",
"jsdom": "^20.0.3",
"kcd-scripts": "^12.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react17": "npm:react@^17.0.2",
"reactDom17": "npm:react-dom@^17.0.2",
"reactIs17": "npm:react-is@^17.0.2",
"reactTesting17": "npm:@testing-library/react@^12.1.3",
"shared-scripts": "^1.5.1",
"typescript": "^4.1.2"
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
}
},
"dependencies": {}
}
1 change: 1 addition & 0 deletions scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "module"}
136 changes: 136 additions & 0 deletions scripts/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import fs from 'fs/promises'
import path from 'path'
import { createBundleBuilder } from '@ph.fritsche/toolbox/dist/builder/index.js'
import { spawn } from 'child_process'

const dirname = path.dirname(new URL(import.meta.url).pathname)
const indexDirLib = path.join(dirname, '../testenv/libs')
const indexDirEnv = path.join(dirname, '../testenv')

const ignoreEnv = ['node.js', 'jest.js']

const cmd = process.argv[2]
const names = process.argv.length > 3 ? process.argv.slice(3) : undefined

if (cmd === 'install-lib') {
await Promise.all(
(await getLibDirs(names))
.map(([name, dir]) => installLib(name, dir))
)
} else if (cmd === 'bundle-lib') {
await Promise.all(
(await getLibDirs(names))
.map(([name, dir]) => buildLib(name, dir))
)
} else if (cmd === 'bundle-env') {
await Promise.all(
(await getEnvFiles(names))
.map(([name, file]) => buildEnv(name, file))
)
} else if (!cmd) {
await Promise.all([
...(await getLibDirs()).map(([name, dir]) => installLib(name, dir).then(() => buildLib(name, dir))),
...(await getEnvFiles()).map(([name, file]) => buildEnv(name, file)),
])
}

async function getLibDirs(names) {
names ??= (await fs.readdir(indexDirLib)).filter(n => !n.startsWith('.'))

return await Promise.all(names.map(name => {
const dir = `${indexDirLib}/${name}`

return fs.stat(`${dir}/index.js`).then(
() => [name, dir],
() => {throw new Error(`${dir}/index.js could not be found.`)}
)
}))
}

async function getEnvFiles(names) {
names ??= (await fs.readdir(indexDirEnv))
.filter(n => /^\w+\.js$/.test(n))
.filter(n => !ignoreEnv.includes(n))
.map(f => f.slice(0, f.length - 3))

return await Promise.all(names.map(async name => {
const file = `${indexDirEnv}/${name}.js`

return fs.stat(file).then(
() => [name, file],
() => { throw new Error(`${file} could not be found.`)}
)
}))
}

async function installLib(name, dir) {
return new Promise((res, rej) => {
const child = spawn('npm', ['i'], {cwd: dir})

process.stdout.write(`Installing library "${name}" at ${dir}\n`)

child.on('error', e => {
process.stdout.write(`${e.stack ?? String(e)}\n`)
})
child.on('exit', (code, signal) => {
(code || signal ? rej(code) : res())
})
})
}

async function buildLib(name, dir) {
const { globals } = JSON.parse(await fs.readFile(`${dir}/package.json`))

process.stdout.write(`Bundling library "${name}" at ${dir}/index.js\n`)

const builder = createBundleBuilder({
basePath: `${dir}/`,
globals,
})
builder.inputFiles.set(`${dir}/index.js`, undefined)

builder.emitter.addListener('complete', e => {
const content = String(e.outputFiles.get('index.js')?.content)
fs.writeFile(`${dir}/index.bundle.js`, content)
.then(() => process.stdout.write([
'<<<',
`Wrote ${dir}/index.bundle.js`,
`[${content.length} bytes]`,
...((globals && Object.keys(globals).length)
? [
` Depending on:`,
...Object.entries(globals).map(([module, name]) => ` ${name} => ${module}`),
]
: []),
'>>>',
'',
].join('\n')))
})

builder.build()
}

async function buildEnv(name, file) {
process.stdout.write(`Bundling environment "${name}" at ${file}\n`)

const builder = createBundleBuilder({
basePath: `${indexDirEnv}/`,
})
const basename = path.basename(file, '.js')
builder.inputFiles.set(file, undefined)

builder.emitter.addListener('complete', e => {
const content = String(e.outputFiles.get(`${basename}.js`)?.content)
fs.writeFile(`${indexDirEnv}/${basename}.bundle.js`, content)
.then(() => process.stdout.write([
'<<<',
`Wrote ${indexDirEnv}/${basename}.bundle.js`,
`[${content.length} bytes]`,
'>>>',
'',
].join('\n')))
})

builder.build()
}

126 changes: 126 additions & 0 deletions scripts/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { createProjectBuildProvider, serveDir, serveToolboxRunner } from '@ph.fritsche/toolbox'
import { NodeTestConductor } from '@ph.fritsche/toolbox/dist/conductor/NodeTestConductor.js'
import { ChromeTestConductor } from '@ph.fritsche/toolbox/dist/conductor/ChromeTestConductor.js'
import { ConsoleReporter } from '@ph.fritsche/toolbox/dist/reporter/ConsoleReporter.js'
import { ReporterServer } from '@ph.fritsche/toolbox/dist/reporter/ReporterServer.js'
import { TestRunStack } from '@ph.fritsche/toolbox/dist/reporter/TestRunStack.js'

import IstanbulLibCoverage from 'istanbul-lib-coverage'
import IstanbulLibReport from 'istanbul-lib-report'
import IstanbulLibSourceMaps from 'istanbul-lib-source-maps'
import IstanbulReports from 'istanbul-reports'

const tsConfigFile = './tests/tsconfig.json'

const toolbox = await serveToolboxRunner()
const env = await serveDir('testenv')

const { buildProvider, fileProvider, fileServer, onBuildDone } = createProjectBuildProvider([
'src',
'tests',
], {
tsConfigFile,
globals: {
'@testing-library/dom': 'DomTestingLibrary',
'@testing-library/react': 'ReactTestingLibrary',
'react': 'React',
'react-dom': 'ReactDom',
}
})

for (const { builder } of buildProvider.builders) {
builder.emitter.addListener('start', ({ type, buildId, inputFiles }) => console.log(builder.id, { type, buildId, inputFiles: inputFiles.size }))
builder.emitter.addListener('complete', ({ type, buildId, inputFiles, outputFiles }) => console.log(builder.id, { type, buildId, inputFiles: inputFiles.size, outputFiles: outputFiles.size }))
builder.emitter.addListener('error', ({ type, buildId, error }) => console.log(builder.id, { type, buildId, error }))
builder.emitter.addListener('done', ({ type, buildId }) => console.log(builder.id, { type, buildId }))
}
buildProvider.getBuilder('dependencies').builder.emitter.addListener('start', ({inputFiles}) => console.log('dependencies', inputFiles.keys()))

const filter = (f) => f.startsWith('tests')
&& /(?<!\.json)\.js$/.test(f)
&& !/\/_.+(?<!\.test)\.[jt]sx?$/.test(f)

const reporterServer = new ReporterServer()
await reporterServer.registerFileServer(toolbox.server)
await reporterServer.registerFileServer(env.server)
await reporterServer.registerFileServer(fileServer)

const consoleReporter = new ConsoleReporter()
consoleReporter.config.result = !!process.env.CI
consoleReporter.connect(reporterServer)

const conductors = [
new ChromeTestConductor(reporterServer, toolbox.url, 'Chrome, DTL8, React18', [
{server: env.url, paths: ['browser.bundle.js']},
{server: env.url, paths: [
'libs/dom8/index.bundle.js',
'libs/react18/index.bundle.js',
]}
]),
new NodeTestConductor(reporterServer, toolbox.url, 'Node, DTL8, React18', [
{server: new URL(`file:https://${env.provider.origin}`), paths: ['node.js']},
{server: env.url, paths: [
'libs/dom8/index.bundle.js',
'libs/react18/index.bundle.js',
]},
]),
]

if (process.env.CI) {
conductors.push(
new NodeTestConductor(reporterServer, toolbox.url, 'Node, DTL8, React17', [
{server: new URL(`file:https://${env.provider.origin}`), paths: ['node.js'] },
{server: env.url, paths: [
'libs/dom8/index.bundle.js',
'libs/react17/index.bundle.js',
]},
])
)
}

onBuildDone(async () => {
const files = {
server: await fileServer.url,
paths: Array.from(fileProvider.files.keys()).filter(filter),
}
const runs = conductors.map(c => c.createTestRun(files))
const stack = new TestRunStack(runs.map(r => r.run))

for (const r of runs) {
await r.exec()
}

await stack.then()

const coverageMap = IstanbulLibCoverage.createCoverageMap()
for (const run of stack.runs) {
for (const coverage of run.coverage.values()) {
coverageMap.merge(coverage)
}
}

const sourceStore = IstanbulLibSourceMaps.createSourceMapStore()
const reportContext = IstanbulLibReport.createContext({
coverageMap: await sourceStore.transformCoverage(coverageMap),
dir: fileProvider.origin,
sourceFinder: sourceStore.sourceFinder,
defaultSummarizer: 'nested',
watermarks: {
branches: [80, 100],
functions: [80, 100],
lines: [80, 100],
statements: [80, 100],
},
})

IstanbulReports.create('text').execute(reportContext)

if (process.env.CI) {
toolbox.server.close()
env.server.close()
fileServer.close()
buildProvider.close()
reporterServer.close()
conductors.forEach(c => c.close())
}
})
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions src/_interop/dtl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */

import def, * as named from '@testing-library/dom'

export default def ?? named
5 changes: 5 additions & 0 deletions src/_interop/dtlEventMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */

import def, * as named from '@testing-library/dom/dist/event-map.js'

export default def ?? named
5 changes: 5 additions & 0 deletions src/_interop/dtlHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */

import def, * as named from '@testing-library/dom/dist/helpers.js'

export default def ?? named
Loading

0 comments on commit e93a5af

Please sign in to comment.