a post-component library for building user-interfaces on the web.
at push-qkwusxtxonpq 105 lines 3.0 kB view raw
1import { createBirpc } from 'birpc' 2import * as fs from 'node:fs/promises' 3import * as path from 'node:path' 4import { fileURLToPath } from 'node:url' 5import { parseArgs, styleText } from 'node:util' 6import { create_browser_runtime } from './browser-runtime.ts' 7import { handle_coverage, type Coverage } from './coverage.ts' 8import * as devalue from './devalue.ts' 9import { create_node_runtime } from './node-runtime.ts' 10import type { ClientFunctions, TestResult } from './runtime.ts' 11 12export interface ServerFunctions { 13 report_result(result: TestResult): void 14} 15 16export interface Runtime { 17 port: MessagePort 18 coverage(): Promise<Coverage[]> 19 [Symbol.asyncDispose](): Promise<void> 20} 21 22const args = parseArgs({ 23 options: { 24 bench: { type: 'boolean', short: 'b', default: false }, 25 prod: { type: 'boolean', short: 'p', default: false }, 26 filter: { type: 'string', short: 'f' }, 27 }, 28 allowPositionals: true, 29}) 30 31const filter = args.values.filter !== undefined ? new RegExp(args.values.filter) : undefined 32 33const all_files: { [runtime: string]: string[] } = {} 34for (const arg of args.positionals) { 35 for await (const file of fs.glob(arg)) { 36 const runtime = file.includes('server') ? 'node' : 'browser' 37 ;(all_files[runtime] ??= []).push(file) 38 } 39} 40 41const results: TestResult[] = [] 42const coverage: Coverage[] = [] 43 44for (const [runtime, files] of Object.entries(all_files)) { 45 const rt = runtime === 'node' ? await create_node_runtime() : await create_browser_runtime() 46 await using _ = rt // workaround for https://issues.chromium.org/issues/409478039 47 48 const client = createBirpc<ClientFunctions, ServerFunctions>( 49 { 50 report_result(run) { 51 if (run.result === 'pass') { 52 console.log(styleText('green', 'PASS'), run.name, styleText('dim', `(${run.duration.toFixed(1)}ms)`)) 53 } else { 54 console.log(styleText('red', 'FAIL'), run.name) 55 console.log(run.reason) 56 console.log() 57 } 58 59 results.push(run) 60 }, 61 }, 62 { 63 post: data => rt.port.postMessage(data), 64 on: fn => { 65 rt.port.onmessage = e => fn(e.data) 66 }, 67 serialize: devalue.stringify, 68 deserialize: devalue.parse, 69 }, 70 ) 71 72 await client.define('__DEV__', !args.values.prod) 73 74 const here = path.join(fileURLToPath(import.meta.url), '..') 75 await Promise.all(files.map(file => client.import('./' + path.relative(here, file)))) 76 77 if (args.values.bench) { 78 await client.run_benchmarks({ filter }) 79 } else { 80 await client.run_tests({ filter }) 81 } 82 83 await client.stop_coverage() 84 coverage.push(...(await rt.coverage())) 85} 86 87if (args.values.bench) { 88} else { 89 await handle_coverage(coverage) 90 91 if (results.length === 0) { 92 console.log('no tests found') 93 process.exitCode = 1 94 } else { 95 const passed = results.reduce((count, { result }) => count + (result === 'pass' ? 1 : 0), 0) 96 const failed = results.reduce((count, { result }) => count + (result === 'fail' ? 1 : 0), 0) 97 98 console.log() 99 console.log(`${passed} passed`) 100 if (failed) { 101 console.log(`${failed} failed`) 102 process.exitCode = 1 103 } 104 } 105}