1'use strict'; 2 3const findYarnWorkspaceRoot = require('find-yarn-workspace-root'); 4const fs = require('fs'); 5const path = require('path'); 6 7module.exports = function(basePreset) { 8 // Explicitly catch and log errors since Jest sometimes suppresses error messages 9 try { 10 return _createJestPreset(basePreset); 11 } catch (e) { 12 console.error(`${e.name}: ${e.message}`); 13 throw e; 14 } 15}; 16 17function _createJestPreset(basePreset) { 18 // Jest does not support chained presets so we flatten this preset before exporting it 19 return { 20 ...basePreset, 21 clearMocks: true, 22 roots: ['<rootDir>/src'], 23 transform: { 24 '^.+\\.jsx?$': 'babel-jest', 25 '^.+\\.tsx?$': [ 26 'ts-jest', 27 { 28 tsconfig: _createTypeScriptConfiguration(), 29 babelConfig: _createBabelConfiguration(), 30 }, 31 ], 32 ...basePreset.transform, 33 }, 34 }; 35} 36 37function _createBabelConfiguration() { 38 // "true" signals that ts-jest should use Babel's default configuration lookup, which will use the 39 // configuration file written above 40 return true; 41} 42 43function _createTypeScriptConfiguration() { 44 let tsConfigFileName = 'tsconfig.json'; 45 // The path to tsconfig.json is resolved relative to cwd 46 let tsConfigPath = path.resolve(tsConfigFileName); 47 48 let jestTsConfigDirectory = path.join('.expo'); 49 let jestTsConfig = { 50 extends: tsConfigPath, 51 compilerOptions: { 52 // Explicitly specify "module" so that ts-jest doesn't provide its default 53 module: 'esnext', 54 // Explicitly specify all the "node_modules/@types" paths up to the workspace or package root 55 typeRoots: [ 56 ..._getDefaultTypeRoots(jestTsConfigDirectory), 57 path.join(__dirname, 'ts-declarations'), 58 ], 59 }, 60 }; 61 62 let jestTsConfigJson = JSON.stringify(jestTsConfig, null, 2); 63 // The TypeScript configuration needs to be under the project directory so that TypeScript finds 64 // type declaration packages that are installed in the project or workspace root (writing it to a 65 // temporary directory would not work, for example) 66 let jestTsConfigPath = path.join(jestTsConfigDirectory, 'tsconfig.jest.json'); 67 68 // NOTE: remove this existsSync call once we require Node 10.12+ 69 if (!fs.existsSync(jestTsConfigPath)) { 70 fs.mkdirSync(path.dirname(jestTsConfigPath), { recursive: true }); 71 } 72 fs.writeFileSync(jestTsConfigPath, jestTsConfigJson); 73 return jestTsConfigPath; 74} 75 76/** 77 * By default, TypeScript looks for type declarations in "node_modules/@types" of the current 78 * directory and all ancestor directories. When overriding the "typeRoots" option, TypeScript no 79 * longer follows this algorithm and we need to re-implement the default behavior. This function 80 * returns the default type roots that TypeScript would have used, except we stop at the workspace 81 * root for better isolation. 82 */ 83function _getDefaultTypeRoots(currentDirectory) { 84 currentDirectory = path.resolve(currentDirectory); 85 let typeRoots = ['./node_modules/@types']; 86 87 // If the TypeScript configuration is in a subdirectory of a package, find the package's directory 88 // since find-yarn-workspace-root works only from workspace packages 89 let packageDirectory = _findPackageDirectory(currentDirectory); 90 if (!packageDirectory) { 91 return typeRoots; 92 } 93 94 let workspaceRootPath = findYarnWorkspaceRoot(packageDirectory); 95 96 // If the TypeScript configuration is in a Yarn workspace, workspace's npm dependencies may be 97 // installed in the workspace root. If the configuration is in a non-workspace package, its 98 // dependencies are installed only in the package's directory. 99 let rootPath = workspaceRootPath || packageDirectory; 100 101 let relativeAncestorDirectoryPath = '..'; 102 while (currentDirectory !== rootPath) { 103 typeRoots.push(path.join(relativeAncestorDirectoryPath, 'node_modules/@types')); 104 currentDirectory = path.dirname(currentDirectory); 105 relativeAncestorDirectoryPath = path.join(relativeAncestorDirectoryPath, '..'); 106 } 107 108 return typeRoots; 109} 110 111function _findPackageDirectory(currentDirectory) { 112 while (currentDirectory !== path.dirname(currentDirectory)) { 113 if (fs.existsSync(path.join(currentDirectory, 'package.json'))) { 114 return currentDirectory; 115 } 116 currentDirectory = path.dirname(currentDirectory); 117 } 118 return null; 119} 120