UNPKG

@typed/test

Version:
175 lines 9.12 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { createServer } from 'http'; import { sync } from 'resolve'; import { findOpenPort } from '../browser/findOpenPort'; import { getLauncher, openBrowser } from '../browser/openBrowser'; import { setupServer } from '../browser/server'; import { basename, join, isAbsolute } from 'path'; import * as tempy from 'tempy'; import { writeFileSync } from 'fs'; import { generateTestBundle } from '../browser/generateTestBundle'; import { createIndexHtml } from '../browser/createIndexHtml'; import { typecheckInAnotherProcess } from '../typescript/typeCheckInAnotherProcess'; import { watchTestMetadata } from '../tests/watchTestMetadata'; import { watchFile } from '../browser/webpack/watchFile'; import { collectByKey } from '../common/collectByKey'; import { Results } from './Results'; import { makeAbsolute } from '../common/makeAbsolute'; import { getTestStats, getTestResults } from '../results'; const isEqual = require('lodash.isequal'); const uniq = (list) => Array.from(new Set(list)); const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const updateQueue = (metadata, queue) => { const queuedMetadataByFilePath = collectByKey(x => x.filePath, queue); const queuedFilePaths = Object.keys(queuedMetadataByFilePath).sort(); const metadataByFilePath = collectByKey(x => x.filePath, metadata); const filePaths = Object.keys(metadataByFilePath).sort(); const allFilePaths = uniq([...filePaths, ...queuedFilePaths]); return allFilePaths.reduce((xs, filePath) => metadataByFilePath[filePath] // replace updated files ? xs.concat(metadataByFilePath[filePath]) : xs.concat(queuedMetadataByFilePath[filePath]), []); }; export function watchBrowserTests(fileGlobs, compilerOptions, options, cwd, logger, cb, errCb, typeCheckCb) { return __awaiter(this, void 0, void 0, function* () { const { keepAlive, timeout, typeCheck, mode } = options; const outputDirectory = tempy.directory(); const temporaryPath = join(outputDirectory, basename(tempy.file({ extension: 'ts' }))); const bundlePath = join(outputDirectory, basename(tempy.file({ extension: 'js' }))); const indexHtmlPath = join(outputDirectory, 'index.html'); const port = yield findOpenPort(); const url = `http://localhost:${port}`; const logErrors = (errors) => Promise.all(errors.map(logger.error)); const makeAbsolutePath = (path) => (isAbsolute(path) ? path : join(cwd, path)); const { updateResults, removeFilePath } = new Results(); // shared mutable state let queuedMetadata = []; let previousMetadata = []; let testsAreRunning = false; let writingToDisk = false; let scheduleNextRunHandle; let newFileOnDisk = false; let killBrowser = () => void 0; let previousWebpackHash = ''; let firstRun = true; let timeToRunTests = 0; let browserOpenTime = 0; let testsCompletedTime = 0; const killTestBrowser = () => (!keepAlive && killBrowser ? killBrowser() : void 0); const setupTestBrowser = () => __awaiter(this, void 0, void 0, function* () { const { browser, keepAlive } = options; if (browser !== 'chrome-headless') { const launcher = yield getLauncher(); const run = () => __awaiter(this, void 0, void 0, function* () { killTestBrowser(); logger.log('Opening browser...'); browserOpenTime = Date.now(); const instance = yield openBrowser(browser, url, keepAlive, launcher); if (!firstRun) { const lastCompletion = testsCompletedTime; // Re-run tests if they seem to have stalled - this can happen when browsers are opened in succession quite quickly // I've only been able to make it do this when I am very rapidly changing and saving a test file to purposely try and break things. delay(timeToRunTests * 3).then(() => (testsCompletedTime === lastCompletion ? run() : void 0)); } killBrowser = () => instance.stop(); }); return run; } const { launch } = require(sync('chrome-launcher', { basedir: cwd })); const run = () => __awaiter(this, void 0, void 0, function* () { killTestBrowser(); logger.log('Opening browser...'); browserOpenTime = Date.now(); const chrome = yield launch({ startingUrl: url }); if (!firstRun) { const lastCompletion = testsCompletedTime; delay(timeToRunTests * 3).then(() => (testsCompletedTime === lastCompletion ? run() : void 0)); } killBrowser = () => chrome.kill(); }); return run; }); const runTestsInBrowser = yield setupTestBrowser(); const newStats = (stats) => __awaiter(this, void 0, void 0, function* () { const shouldReturnEarly = stats.hash === previousWebpackHash || !newFileOnDisk || testsAreRunning; if (shouldReturnEarly) { previousWebpackHash = stats.hash; return; } if (stats.hasErrors()) { const { errors } = stats.toJson(); return logErrors(errors); } testsAreRunning = true; runTestsInBrowser(); }); const writeToDisk = (metadata) => { writeFileSync(temporaryPath, generateTestBundle(cwd, outputDirectory, port, timeout, metadata)); newFileOnDisk = true; writingToDisk = false; if (firstRun) { watchFile(cwd, temporaryPath, bundlePath, options.webpackConfiguration, newStats, errCb); } }; const typeCheckMetadata = (metadata) => __awaiter(this, void 0, void 0, function* () { logger.log('Typechecking...'); const processResults = yield typecheckInAnotherProcess(cwd, metadata.map(x => x.filePath)); logger.log('Type checking complete'); typeCheckCb(processResults); }); const removeFileFromQueue = (filePath) => { const path = makeAbsolute(cwd, filePath); removeFilePath(path); queuedMetadata = queuedMetadata.filter(x => makeAbsolutePath(x.filePath) !== path); }; const scheduleNextBundleWrite = () => { if (testsAreRunning || writingToDisk) { clearTimeout(scheduleNextRunHandle); scheduleNextRunHandle = setTimeout(scheduleNextBundleWrite, 600); return; } writingToDisk = true; const currentQueue = queuedMetadata.slice(); writeToDisk(currentQueue); if (typeCheck) { typeCheckMetadata(currentQueue); } queuedMetadata = []; }; const updateMetadataAndWriteBundle = (metadata) => __awaiter(this, void 0, void 0, function* () { queuedMetadata = updateQueue(metadata, queuedMetadata); if (isEqual(metadata, previousMetadata) || queuedMetadata.length === 0) { return; } previousMetadata = metadata; scheduleNextBundleWrite(); }); const server = createServer(setupServer(logger, outputDirectory, newResults => { firstRun = false; if (!keepAlive && killBrowser) { killBrowser(); } const results = updateResults(newResults); const stats = getTestStats(getTestResults(results)); cb({ results, stats }); testsCompletedTime = Date.now(); timeToRunTests = testsCompletedTime - browserOpenTime; newFileOnDisk = testsAreRunning = false; })); writeFileSync(indexHtmlPath, createIndexHtml(basename(bundlePath))); server.listen(port, '0.0.0.0'); const { dispose: stopWatchingMetadata } = yield watchTestMetadata(cwd, fileGlobs, compilerOptions, mode, logger, removeFileFromQueue, updateMetadataAndWriteBundle); const dispose = () => { stopWatchingMetadata(); server.close(); }; return { dispose }; }); } //# sourceMappingURL=watchBrowserTests.js.map