import * as plugins from './tstest.plugins.js'; import * as paths from './tstest.paths.js'; import * as logPrefixes from './tstest.logprefixes.js'; import { coloredString as cs } from '@push.rocks/consolecolor'; import { TestDirectory } from './tstest.classes.testdirectory.js'; import { TapCombinator } from './tstest.classes.tap.combinator.js'; import { TapParser } from './tstest.classes.tap.parser.js'; export class TsTest { public testDir: TestDirectory; public smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', pathDirectories: [paths.binDirectory], sourceFilePaths: [], }); public smartbrowserInstance = new plugins.smartbrowser.SmartBrowser(); public tsbundleInstance = new plugins.tsbundle.TsBundle(); constructor(cwdArg: string, relativePathToTestDirectory: string) { this.testDir = new TestDirectory(cwdArg, relativePathToTestDirectory); } async run() { const fileNamesToRun: string[] = await this.testDir.getTestFilePathArray(); console.log(cs(plugins.figures.hamburger.repeat(80), 'cyan')); console.log(''); console.log(`${logPrefixes.TsTestPrefix} FOUND ${fileNamesToRun.length} TESTFILE(S):`); for (const fileName of fileNamesToRun) { console.log(`${logPrefixes.TsTestPrefix} ${cs(fileName, 'orange')}`); } console.log('-'.repeat(48)); console.log(''); // force new line const tapCombinator = new TapCombinator(); // lets create the TapCombinator for (const fileNameArg of fileNamesToRun) { switch (true) { case process.env.CI && fileNameArg.includes('.nonci.'): console.log('!!!!!!!!!!!'); console.log( `not running testfile ${fileNameArg}, since we are CI and file name includes '.nonci.' tag` ); console.log('!!!!!!!!!!!'); break; case fileNameArg.endsWith('.browser.ts') || fileNameArg.endsWith('.browser.nonci.ts'): const tapParserBrowser = await this.runInChrome(fileNameArg); tapCombinator.addTapParser(tapParserBrowser); break; case fileNameArg.endsWith('.both.ts') || fileNameArg.endsWith('.both.nonci.ts'): console.log('>>>>>>> TEST PART 1: chrome'); const tapParserBothBrowser = await this.runInChrome(fileNameArg); tapCombinator.addTapParser(tapParserBothBrowser); console.log(cs(`|`.repeat(16), 'cyan')); console.log(''); // force new line console.log('>>>>>>> TEST PART 2: node'); const tapParserBothNode = await this.runInNode(fileNameArg); tapCombinator.addTapParser(tapParserBothNode); break; default: const tapParserNode = await this.runInNode(fileNameArg); tapCombinator.addTapParser(tapParserNode); break; } console.log(cs(`^`.repeat(16), 'cyan')); console.log(''); // force new line } tapCombinator.evaluate(); } public async runInNode(fileNameArg: string): Promise { console.log(`${cs('=> ', 'blue')} Running ${cs(fileNameArg, 'orange')} in node.js runtime.`); console.log(`${cs(`= `.repeat(32), 'cyan')}`); const tapParser = new TapParser(fileNameArg + ':node'); // tsrun options let tsrunOptions = ''; if (process.argv.includes('--web')) { tsrunOptions += ' --web'; } const execResultStreaming = await this.smartshellInstance.execStreamingSilent( `tsrun ${fileNameArg}${tsrunOptions}` ); await tapParser.handleTapProcess(execResultStreaming.childProcess); return tapParser; } public async runInChrome(fileNameArg: string): Promise { console.log(`${cs('=> ', 'blue')} Running ${cs(fileNameArg, 'orange')} in chromium runtime.`); console.log(`${cs(`= `.repeat(32), 'cyan')}`); // lets get all our paths sorted const tsbundleCacheDirPath = plugins.path.join(paths.cwd, './.nogit/tstest_cache'); const bundleFileName = fileNameArg.replace('/', '__') + '.js'; const bundleFilePath = plugins.path.join(tsbundleCacheDirPath, bundleFileName); // lets bundle the test await plugins.smartfile.fs.ensureEmptyDir(tsbundleCacheDirPath); await this.tsbundleInstance.build(process.cwd(), fileNameArg, bundleFilePath, { bundler: 'esbuild', }); // lets create a server const server = new plugins.typedserver.servertools.Server({ cors: true, port: 3007, }); server.addRoute( '/test', new plugins.typedserver.servertools.Handler('GET', async (req, res) => { res.type('.html'); res.write(` `); res.end(); }) ); server.addRoute('*', new plugins.typedserver.servertools.HandlerStatic(tsbundleCacheDirPath)); await server.start(); // lets handle realtime comms const tapParser = new TapParser(fileNameArg + ':chrome'); const wss = new plugins.ws.WebSocketServer({ port: 8080 }); wss.on('connection', (ws) => { ws.on('message', (message) => { tapParser.handleTapLog(message.toString()); }); }); // lets do the browser bit await this.smartbrowserInstance.start(); const evaluation = await this.smartbrowserInstance.evaluateOnPage( `http://localhost:3007/test?bundleName=${bundleFileName}`, async () => { // lets enable real time comms const ws = new WebSocket('ws://localhost:8080'); await new Promise((resolve) => (ws.onopen = resolve)); // Ensure this function is declared with 'async' const logStore = []; const originalLog = console.log; const originalError = console.error; // Override console methods to capture the logs console.log = (...args) => { logStore.push(args.join(' ')); ws.send(args.join(' ')); originalLog(...args); }; console.error = (...args) => { logStore.push(args.join(' ')); ws.send(args.join(' ')); originalError(...args); }; const bundleName = new URLSearchParams(window.location.search).get('bundleName'); originalLog(`::TSTEST IN CHROMIUM:: Relevant Script name is: ${bundleName}`); try { // Dynamically import the test module const testModule = await import(`/${bundleName}`); if (testModule && testModule.runTestPromise) { // Execute the exported test function await testModule.runTestPromise; } else { console.error('Test module does not export runTest function.'); } } catch (err) { console.error(err); } return logStore.join('\n'); } ); await this.smartbrowserInstance.stop(); await server.stop(); wss.close(); console.log( `${cs('=> ', 'blue')} Stopped ${cs(fileNameArg, 'orange')} chromium instance and server.` ); // lets create the tap parser await tapParser.evaluateFinalResult(); return tapParser; } public async runInDeno() {} }