UNPKG

ohayolibs

Version:

Ohayo is a set of essential modules for ohayojp.

140 lines (117 loc) 6.21 kB
// tslint:disable:no-string-literal import { getSystemPath, normalize } from '@angular-devkit/core'; import { TempScopedNodeJsSyncHost } from '@angular-devkit/core/node/testing'; import * as virtualFs from '@angular-devkit/core/src/virtual-fs/host'; import { EngineHost, TaskExecutor, TaskScheduler } from '@angular-devkit/schematics'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { mkdirpSync, readFileSync, writeFileSync } from 'fs-extra'; import { dirname, join } from 'path'; import { from as observableFrom, Observable } from 'rxjs'; import { concatMap, filter, last } from 'rxjs/operators'; /** Path to the schematic collection that includes the ohayo migrations. */ export const migrationCollection = require.resolve('../../migration.json'); /** Module name suffix for data files of the `jasmine_node_test` Bazel rule. */ const bazelModuleSuffix = 'ohayo/packages/schematics/ng-update/test-cases'; /** * Due to the fact that the Angular devkit does not support running scheduled tasks from a * schematic that has been launched through the TestRunner, we need to manually find the task * executor for the given task name and run all scheduled instances. * * Note that this means that there can be multiple tasks with the same name. The observable emits * only when all tasks finished executing. */ export function runPostScheduledTasks(runner: SchematicTestRunner, taskName: string): Observable<any> { // Workaround until there is a public API to run scheduled tasks in the @angular-devkit. // See: https://github.com/angular/angular-cli/issues/11739 const host = runner.engine['_host'] as EngineHost<{}, {}>; const tasks = runner.engine['_taskSchedulers'] as TaskScheduler[]; const createTaskExecutor = (name: string) => (host.createTaskExecutor(name) as any) as Observable<TaskExecutor<any>>; return observableFrom(tasks).pipe( concatMap(scheduler => scheduler.finalize()), filter(task => task.configuration.name === taskName), concatMap(task => { return createTaskExecutor(task.configuration.name).pipe( concatMap(executor => executor(task.configuration.options, task.context)), ); }), // Only emit the last emitted value because there can be multiple tasks with the same name. // The observable should only emit a value if all tasks completed. last(), ); } export async function createTestApp(runner: SchematicTestRunner, appOptions: {} = {}): Promise<UnitTestTree> { const workspaceTree = await runner.runExternalSchematicAsync('@schematics/angular', 'workspace', { name: 'workspace', version: '6.0.0', newProjectRoot: 'projects', }).toPromise(); const res = await runner.runExternalSchematicAsync( '@schematics/angular', 'application', { ...appOptions, name: 'ohayojp' }, workspaceTree, ).toPromise(); return res; } /** Reads the UTF8 content of the specified file. Normalizes the path and ensures that */ export function readFileContent(filePath: string): string { return readFileSync(filePath, 'utf8'); } /** * Resolves the original file path of the specified file that has been to the `data` of the * jasmine_node_test Bazel rule. * * Adding the test case files to the data of the `jasmine_node_test` Bazel rule does not mean * that the files are being copied over to the Bazel bin output. Bazel just patches the NodeJS * resolve function and maps the module paths to the original file location. */ export function resolveBazelDataFile(filePath: string): string { return require.resolve(`./${filePath}`); // Only for bazel return require.resolve(`${bazelModuleSuffix}/${filePath}`); } /** * Creates a test app schematic tree that will be copied over to a real filesystem location. * This is necessary because TSLint is not able to read from the virtual filesystem tree. */ export async function createFileSystemTestApp(runner: SchematicTestRunner): Promise<{ tree: UnitTestTree, tempPath: string }> { const tempFileSystemHost = new TempScopedNodeJsSyncHost(); const tree = await createTestApp(runner); const tempPath = getSystemPath(tempFileSystemHost.root); // Since the TSLint fix task expects all files to be present on the real file system, we // map every file in the app tree to a temporary location on the file system. tree.files .map(f => normalize(f)) .forEach(f => { tempFileSystemHost.sync.write(f, virtualFs.stringToFileBuffer(tree.readContent(f))); }); return { tree, tempPath }; } export async function runTestCases(migrationName: string, inputs: { [name: string]: string }): Promise<{ tempPath: string; logOutput: string; appTree: UnitTestTree; }> { const runner = new SchematicTestRunner('schematics', migrationCollection); const inputNames = Object.keys(inputs); const initialWorkingDir = process.cwd(); let logOutput = ''; runner.logger.subscribe(entry => (logOutput += entry.message)); const { tree, tempPath } = await createFileSystemTestApp(runner); // Write each test-case input to the file-system. This is necessary because otherwise // TSLint won't be able to pick up the test cases. inputNames.forEach(inputName => { const inputFullName = `projects/ohayojp/src/test-cases/${inputName}.ts`; const tempInputPath = join(tempPath, inputFullName); mkdirpSync(dirname(tempInputPath)); writeFileSync(tempInputPath, readFileContent(inputs[inputName])); // TODO: add to tree tree.create(inputFullName, readFileContent(inputs[inputName])); }); await runner.runSchematicAsync(migrationName, {}, tree).toPromise(); // Switch to the new temporary directory because otherwise TSLint cannot read the files. process.chdir(tempPath); // Run the scheduled TSLint fix task from the update schematic. This task is responsible for // identifying outdated code parts and performs the fixes. Since tasks won't run automatically // within a `SchematicTestRunner`, we manually need to run the scheduled task. await runPostScheduledTasks(runner, 'tslint-fix').toPromise(); // Switch back to the initial working directory. process.chdir(initialWorkingDir); return { tempPath, logOutput, appTree: tree }; }