@typed/test
Version:
Testing made simple.
182 lines • 9.83 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const http_1 = require("http");
const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
const path_1 = require("path");
const resolve_1 = require("resolve");
const tempy_1 = __importDefault(require("tempy"));
const createIndexHtml_1 = require("../browser/createIndexHtml");
const findOpenPort_1 = require("../browser/findOpenPort");
const generateTestBundle_1 = require("../browser/generateTestBundle");
const openBrowser_1 = require("../browser/openBrowser");
const server_1 = require("../browser/server");
const watchFile_1 = require("../browser/webpack/watchFile");
const collectByKey_1 = require("../common/collectByKey");
const makeAbsolute_1 = require("../common/makeAbsolute");
const results_1 = require("../results");
const watchTestMetadata_1 = require("../tests/watchTestMetadata");
const typeCheckInAnotherProcess_1 = require("../typescript/typeCheckInAnotherProcess");
const Results_1 = require("./Results");
const uniq = (list) => Array.from(new Set(list));
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const updateQueue = (metadata, queue) => {
const queuedMetadataByFilePath = collectByKey_1.collectByKey(x => x.filePath, queue);
const queuedFilePaths = Object.keys(queuedMetadataByFilePath).sort();
const metadataByFilePath = collectByKey_1.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]), []);
};
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_1.default.directory();
const temporaryPath = path_1.join(outputDirectory, path_1.basename(tempy_1.default.file({ extension: 'ts' })));
const bundlePath = path_1.join(outputDirectory, path_1.basename(tempy_1.default.file({ extension: 'js' })));
const indexHtmlPath = path_1.join(outputDirectory, 'index.html');
const port = yield findOpenPort_1.findOpenPort();
const url = `http://localhost:${port}`;
const logErrors = (errors) => Promise.all(errors.map(logger.error));
const makeAbsolutePath = (path) => (path_1.isAbsolute(path) ? path : path_1.join(cwd, path));
const { updateResults, removeFilePath } = new Results_1.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 openBrowser_1.getLauncher();
const run = () => __awaiter(this, void 0, void 0, function* () {
killTestBrowser();
logger.log('Opening browser...');
browserOpenTime = Date.now();
const instance = yield openBrowser_1.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(resolve_1.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) => {
fs_1.writeFileSync(temporaryPath, generateTestBundle_1.generateTestBundle(cwd, outputDirectory, port, timeout, metadata));
newFileOnDisk = true;
writingToDisk = false;
if (firstRun) {
watchFile_1.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_1.typecheckInAnotherProcess(cwd, metadata.map(x => x.filePath));
logger.log('Type checking complete');
typeCheckCb(processResults);
});
const removeFileFromQueue = (filePath) => {
const path = makeAbsolute_1.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 (lodash_isequal_1.default(metadata, previousMetadata) || queuedMetadata.length === 0) {
return;
}
previousMetadata = metadata;
scheduleNextBundleWrite();
});
const server = http_1.createServer(server_1.setupServer(logger, outputDirectory, newResults => {
firstRun = false;
if (!keepAlive && killBrowser) {
killBrowser();
}
const results = updateResults(newResults);
const stats = results_1.getTestStats(results_1.getTestResults(results));
cb({ results, stats });
testsCompletedTime = Date.now();
timeToRunTests = testsCompletedTime - browserOpenTime;
newFileOnDisk = testsAreRunning = false;
}));
fs_1.writeFileSync(indexHtmlPath, createIndexHtml_1.createIndexHtml(path_1.basename(bundlePath)));
server.listen(port, '0.0.0.0');
const { dispose: stopWatchingMetadata } = yield watchTestMetadata_1.watchTestMetadata(cwd, fileGlobs, compilerOptions, mode, logger, removeFileFromQueue, updateMetadataAndWriteBundle);
const dispose = () => {
stopWatchingMetadata();
server.close();
};
return { dispose };
});
}
exports.watchBrowserTests = watchBrowserTests;
//# sourceMappingURL=watchBrowserTests.js.map