Skip to content

Commit e93a5af

Browse files
authored
chore: rewrite tests (#1081)
* remove prop tracking * import cjs modules per interop * remove dependency on jest environment * add test environments and scripts * disable coverage threshold
1 parent 1aa2027 commit e93a5af

Some content is hidden

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

69 files changed

+1028
-367
lines changed

β€Ž.eslintignoreβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/coverage
22
/dist
33
/node_modules
4+
/testenv
5+
/scripts

β€Ž.eslintrc.jsβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,11 @@ module.exports = {
2323
'local-rules/explicit-globals': 'error',
2424
},
2525
},
26+
{
27+
files: ['**.{ts,tsx}'],
28+
rules: {
29+
'@typescript-eslint/no-unsafe-argument': 1,
30+
},
31+
},
2632
],
2733
}

β€Ž.github/workflows/ci.ymlβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131
run: npm run lint
3232
- name: πŸ§ͺ Test
3333
run: npm run test -- --coverage
34+
- name: 🚧 Build test environments
35+
run: npm run setup:env
36+
- name: πŸ”¬ Test with toolbox
37+
run: npm run test:toolbox
3438
- name: πŸ— Build
3539
run: npm run build
3640

β€Žjest.config.jsβ€Ž

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ config.moduleNameMapper = {
1111
config.testEnvironment = 'jsdom'
1212

1313
config.setupFilesAfterEnv = [
14-
'<rootDir>/tests/_setup-env.js',
15-
'<rootDir>/tests/react/_env/setup-env.js',
14+
'<rootDir>/testenv/jest.js',
1615
]
1716

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

26+
config.snapshotSerializers = [
27+
require.resolve('jest-snapshot-serializer-raw/always'),
28+
]
29+
30+
config.coverageThreshold = undefined
31+
2732
module.exports = config

β€Žpackage.jsonβ€Ž

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,45 @@
2929
"build": "scripts ts-build2 --cjs --target es2019",
3030
"lint": "kcd-scripts lint",
3131
"setup": "npm install && npm run validate -s",
32+
"setup:env": "node scripts/setup.js",
3233
"test": "kcd-scripts test",
34+
"test:jest": "kcd-scripts test",
35+
"test:toolbox": "NODE_OPTIONS='--experimental-vm-modules --experimental-modules --experimental-import-meta-resolve' node scripts/test.js",
3336
"test:debug": "kcd-scripts --inspect-brk test --runInBand",
3437
"test:update": "npm test -- --updateSnapshot --coverage",
3538
"validate": "kcd-scripts typecheck"
3639
},
3740
"devDependencies": {
3841
"@ph.fritsche/scripts-config": "^2.4.0",
39-
"@testing-library/dom": "^8.11.4",
42+
"@ph.fritsche/toolbox": "^1.0.0-alpha.1",
43+
"@testing-library/dom": "^8.19.0",
4044
"@testing-library/jest-dom": "^5.16.3",
41-
"@testing-library/react": "^13.0.0",
45+
"@testing-library/react": "^13.4.0",
4246
"@types/jest-in-case": "^1.0.3",
43-
"@types/react": "^17.0.42",
44-
"eslint-import-resolver-typescript": "^2.7.0",
45-
"eslint-plugin-local-rules": "^1.1.0",
47+
"@types/react": "^18.0.25",
48+
"@types/sinonjs__fake-timers": "^8.1.2",
49+
"css.escape": "^1.5.1",
50+
"eslint-import-resolver-typescript": "^3.5.2",
51+
"eslint-plugin-local-rules": "^1.3.2",
52+
"expect": "^28.1.3",
4653
"is-ci": "^3.0.1",
54+
"istanbul-lib-coverage": "^3.2.0",
55+
"istanbul-lib-report": "^3.0.0",
56+
"istanbul-lib-source-maps": "^4.0.1",
57+
"istanbul-reports": "^3.1.5",
4758
"jest-in-case": "^1.0.2",
59+
"jest-mock": "^28.1.3",
4860
"jest-serializer-ansi": "^1.0.3",
61+
"jsdom": "^20.0.3",
4962
"kcd-scripts": "^12.1.0",
5063
"react": "^18.0.0",
5164
"react-dom": "^18.0.0",
52-
"react17": "npm:react@^17.0.2",
53-
"reactDom17": "npm:react-dom@^17.0.2",
54-
"reactIs17": "npm:react-is@^17.0.2",
55-
"reactTesting17": "npm:@testing-library/react@^12.1.3",
5665
"shared-scripts": "^1.5.1",
57-
"typescript": "^4.1.2"
66+
"ts-node": "^10.9.1",
67+
"typescript": "^4.9.3"
5868
},
5969
"peerDependencies": {
6070
"@testing-library/dom": ">=7.21.4"
61-
}
71+
},
72+
"dependencies": {}
6273
}

β€Žscripts/package.jsonβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"type": "module"}

β€Žscripts/setup.jsβ€Ž

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import fs from 'fs/promises'
2+
import path from 'path'
3+
import { createBundleBuilder } from '@ph.fritsche/toolbox/dist/builder/index.js'
4+
import { spawn } from 'child_process'
5+
6+
const dirname = path.dirname(new URL(import.meta.url).pathname)
7+
const indexDirLib = path.join(dirname, '../testenv/libs')
8+
const indexDirEnv = path.join(dirname, '../testenv')
9+
10+
const ignoreEnv = ['node.js', 'jest.js']
11+
12+
const cmd = process.argv[2]
13+
const names = process.argv.length > 3 ? process.argv.slice(3) : undefined
14+
15+
if (cmd === 'install-lib') {
16+
await Promise.all(
17+
(await getLibDirs(names))
18+
.map(([name, dir]) => installLib(name, dir))
19+
)
20+
} else if (cmd === 'bundle-lib') {
21+
await Promise.all(
22+
(await getLibDirs(names))
23+
.map(([name, dir]) => buildLib(name, dir))
24+
)
25+
} else if (cmd === 'bundle-env') {
26+
await Promise.all(
27+
(await getEnvFiles(names))
28+
.map(([name, file]) => buildEnv(name, file))
29+
)
30+
} else if (!cmd) {
31+
await Promise.all([
32+
...(await getLibDirs()).map(([name, dir]) => installLib(name, dir).then(() => buildLib(name, dir))),
33+
...(await getEnvFiles()).map(([name, file]) => buildEnv(name, file)),
34+
])
35+
}
36+
37+
async function getLibDirs(names) {
38+
names ??= (await fs.readdir(indexDirLib)).filter(n => !n.startsWith('.'))
39+
40+
return await Promise.all(names.map(name => {
41+
const dir = `${indexDirLib}/${name}`
42+
43+
return fs.stat(`${dir}/index.js`).then(
44+
() => [name, dir],
45+
() => {throw new Error(`${dir}/index.js could not be found.`)}
46+
)
47+
}))
48+
}
49+
50+
async function getEnvFiles(names) {
51+
names ??= (await fs.readdir(indexDirEnv))
52+
.filter(n => /^\w+\.js$/.test(n))
53+
.filter(n => !ignoreEnv.includes(n))
54+
.map(f => f.slice(0, f.length - 3))
55+
56+
return await Promise.all(names.map(async name => {
57+
const file = `${indexDirEnv}/${name}.js`
58+
59+
return fs.stat(file).then(
60+
() => [name, file],
61+
() => { throw new Error(`${file} could not be found.`)}
62+
)
63+
}))
64+
}
65+
66+
async function installLib(name, dir) {
67+
return new Promise((res, rej) => {
68+
const child = spawn('npm', ['i'], {cwd: dir})
69+
70+
process.stdout.write(`Installing library "${name}" at ${dir}\n`)
71+
72+
child.on('error', e => {
73+
process.stdout.write(`${e.stack ?? String(e)}\n`)
74+
})
75+
child.on('exit', (code, signal) => {
76+
(code || signal ? rej(code) : res())
77+
})
78+
})
79+
}
80+
81+
async function buildLib(name, dir) {
82+
const { globals } = JSON.parse(await fs.readFile(`${dir}/package.json`))
83+
84+
process.stdout.write(`Bundling library "${name}" at ${dir}/index.js\n`)
85+
86+
const builder = createBundleBuilder({
87+
basePath: `${dir}/`,
88+
globals,
89+
})
90+
builder.inputFiles.set(`${dir}/index.js`, undefined)
91+
92+
builder.emitter.addListener('complete', e => {
93+
const content = String(e.outputFiles.get('index.js')?.content)
94+
fs.writeFile(`${dir}/index.bundle.js`, content)
95+
.then(() => process.stdout.write([
96+
'<<<',
97+
`Wrote ${dir}/index.bundle.js`,
98+
`[${content.length} bytes]`,
99+
...((globals && Object.keys(globals).length)
100+
? [
101+
` Depending on:`,
102+
...Object.entries(globals).map(([module, name]) => ` ${name} => ${module}`),
103+
]
104+
: []),
105+
'>>>',
106+
'',
107+
].join('\n')))
108+
})
109+
110+
builder.build()
111+
}
112+
113+
async function buildEnv(name, file) {
114+
process.stdout.write(`Bundling environment "${name}" at ${file}\n`)
115+
116+
const builder = createBundleBuilder({
117+
basePath: `${indexDirEnv}/`,
118+
})
119+
const basename = path.basename(file, '.js')
120+
builder.inputFiles.set(file, undefined)
121+
122+
builder.emitter.addListener('complete', e => {
123+
const content = String(e.outputFiles.get(`${basename}.js`)?.content)
124+
fs.writeFile(`${indexDirEnv}/${basename}.bundle.js`, content)
125+
.then(() => process.stdout.write([
126+
'<<<',
127+
`Wrote ${indexDirEnv}/${basename}.bundle.js`,
128+
`[${content.length} bytes]`,
129+
'>>>',
130+
'',
131+
].join('\n')))
132+
})
133+
134+
builder.build()
135+
}
136+

β€Žscripts/test.jsβ€Ž

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { createProjectBuildProvider, serveDir, serveToolboxRunner } from '@ph.fritsche/toolbox'
2+
import { NodeTestConductor } from '@ph.fritsche/toolbox/dist/conductor/NodeTestConductor.js'
3+
import { ChromeTestConductor } from '@ph.fritsche/toolbox/dist/conductor/ChromeTestConductor.js'
4+
import { ConsoleReporter } from '@ph.fritsche/toolbox/dist/reporter/ConsoleReporter.js'
5+
import { ReporterServer } from '@ph.fritsche/toolbox/dist/reporter/ReporterServer.js'
6+
import { TestRunStack } from '@ph.fritsche/toolbox/dist/reporter/TestRunStack.js'
7+
8+
import IstanbulLibCoverage from 'istanbul-lib-coverage'
9+
import IstanbulLibReport from 'istanbul-lib-report'
10+
import IstanbulLibSourceMaps from 'istanbul-lib-source-maps'
11+
import IstanbulReports from 'istanbul-reports'
12+
13+
const tsConfigFile = './tests/tsconfig.json'
14+
15+
const toolbox = await serveToolboxRunner()
16+
const env = await serveDir('testenv')
17+
18+
const { buildProvider, fileProvider, fileServer, onBuildDone } = createProjectBuildProvider([
19+
'src',
20+
'tests',
21+
], {
22+
tsConfigFile,
23+
globals: {
24+
'@testing-library/dom': 'DomTestingLibrary',
25+
'@testing-library/react': 'ReactTestingLibrary',
26+
'react': 'React',
27+
'react-dom': 'ReactDom',
28+
}
29+
})
30+
31+
for (const { builder } of buildProvider.builders) {
32+
builder.emitter.addListener('start', ({ type, buildId, inputFiles }) => console.log(builder.id, { type, buildId, inputFiles: inputFiles.size }))
33+
builder.emitter.addListener('complete', ({ type, buildId, inputFiles, outputFiles }) => console.log(builder.id, { type, buildId, inputFiles: inputFiles.size, outputFiles: outputFiles.size }))
34+
builder.emitter.addListener('error', ({ type, buildId, error }) => console.log(builder.id, { type, buildId, error }))
35+
builder.emitter.addListener('done', ({ type, buildId }) => console.log(builder.id, { type, buildId }))
36+
}
37+
buildProvider.getBuilder('dependencies').builder.emitter.addListener('start', ({inputFiles}) => console.log('dependencies', inputFiles.keys()))
38+
39+
const filter = (f) => f.startsWith('tests')
40+
&& /(?<!\.json)\.js$/.test(f)
41+
&& !/\/_.+(?<!\.test)\.[jt]sx?$/.test(f)
42+
43+
const reporterServer = new ReporterServer()
44+
await reporterServer.registerFileServer(toolbox.server)
45+
await reporterServer.registerFileServer(env.server)
46+
await reporterServer.registerFileServer(fileServer)
47+
48+
const consoleReporter = new ConsoleReporter()
49+
consoleReporter.config.result = !!process.env.CI
50+
consoleReporter.connect(reporterServer)
51+
52+
const conductors = [
53+
new ChromeTestConductor(reporterServer, toolbox.url, 'Chrome, DTL8, React18', [
54+
{server: env.url, paths: ['browser.bundle.js']},
55+
{server: env.url, paths: [
56+
'libs/dom8/index.bundle.js',
57+
'libs/react18/index.bundle.js',
58+
]}
59+
]),
60+
new NodeTestConductor(reporterServer, toolbox.url, 'Node, DTL8, React18', [
61+
{server: new URL(`file://${env.provider.origin}`), paths: ['node.js']},
62+
{server: env.url, paths: [
63+
'libs/dom8/index.bundle.js',
64+
'libs/react18/index.bundle.js',
65+
]},
66+
]),
67+
]
68+
69+
if (process.env.CI) {
70+
conductors.push(
71+
new NodeTestConductor(reporterServer, toolbox.url, 'Node, DTL8, React17', [
72+
{server: new URL(`file://${env.provider.origin}`), paths: ['node.js'] },
73+
{server: env.url, paths: [
74+
'libs/dom8/index.bundle.js',
75+
'libs/react17/index.bundle.js',
76+
]},
77+
])
78+
)
79+
}
80+
81+
onBuildDone(async () => {
82+
const files = {
83+
server: await fileServer.url,
84+
paths: Array.from(fileProvider.files.keys()).filter(filter),
85+
}
86+
const runs = conductors.map(c => c.createTestRun(files))
87+
const stack = new TestRunStack(runs.map(r => r.run))
88+
89+
for (const r of runs) {
90+
await r.exec()
91+
}
92+
93+
await stack.then()
94+
95+
const coverageMap = IstanbulLibCoverage.createCoverageMap()
96+
for (const run of stack.runs) {
97+
for (const coverage of run.coverage.values()) {
98+
coverageMap.merge(coverage)
99+
}
100+
}
101+
102+
const sourceStore = IstanbulLibSourceMaps.createSourceMapStore()
103+
const reportContext = IstanbulLibReport.createContext({
104+
coverageMap: await sourceStore.transformCoverage(coverageMap),
105+
dir: fileProvider.origin,
106+
sourceFinder: sourceStore.sourceFinder,
107+
defaultSummarizer: 'nested',
108+
watermarks: {
109+
branches: [80, 100],
110+
functions: [80, 100],
111+
lines: [80, 100],
112+
statements: [80, 100],
113+
},
114+
})
115+
116+
IstanbulReports.create('text').execute(reportContext)
117+
118+
if (process.env.CI) {
119+
toolbox.server.close()
120+
env.server.close()
121+
fileServer.close()
122+
buildProvider.close()
123+
reporterServer.close()
124+
conductors.forEach(c => c.close())
125+
}
126+
})
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
Β (0)