├── .babelrc ├── .eslintrc.yml ├── .gitignore ├── .prettierrc.yml ├── .travis.yml ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── index.js └── runTsc.js ├── test ├── index.test.js ├── lib-bad.test.ts ├── lib-good.test.ts └── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - eslint:recommended 3 | - plugin:jest/recommended 4 | - plugin:prettier/recommended 5 | env: 6 | es6: true 7 | node: true 8 | parserOptions: 9 | ecmaVersion: 2018 10 | overrides: 11 | - files: "src/**/*.js" 12 | parserOptions: 13 | sourceType: module 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea/ -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | trailingComma: es5 2 | singleQuote: true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '8' 10 | - '10' 11 | - '11' 12 | install: 13 | - yarn 14 | script: 15 | - yarn build 16 | - yarn test 17 | after_success: 18 | - yarn semantic-release 19 | branches: 20 | except: 21 | - /^v\d+\.\d+\.\d+$/ 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lucas Azzola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `jest-runner-tsc` 2 | 3 | [![Travis](https://img.shields.io/travis/azz/jest-runner-tsc.svg?style=flat-square)](https://travis-ci.org/azz/jest-runner-tsc) 4 | [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 5 | [![npm](https://img.shields.io/npm/v/jest-runner-tsc.svg?style=flat-square)](https://npmjs.org/jest-runner-tsc) 6 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 8 | 9 | > A Jest runner for the TypeScript compiler 10 | 11 | ## install 12 | 13 | ```bash 14 | npm install --save-dev jest-runner-tsc 15 | ``` 16 | 17 | ## usage 18 | 19 | Jest configuration: 20 | 21 | jest.tsc.config.js: 22 | 23 | ```js 24 | module.exports = { 25 | runner: 'jest-runner-tsc', 26 | displayName: 'tsc', 27 | moduleFileExtensions: ['js','ts', 'tsx'], 28 | testMatch: ['/**/*.ts'], 29 | }; 30 | ``` 31 | 32 | ## options 33 | 34 | This project uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), so you can provide config via: 35 | 36 | - a `jest-runner-tsc` property in your package.json 37 | - a `jest-runner-tsc.config.js` JS file 38 | - a `.jest-runner-tscrc` JSON file 39 | 40 | ### Example in package.json 41 | 42 | ```json 43 | { 44 | "jest-runner-tsc": { 45 | "tsconfigPath": "./tsconfig.types.json" 46 | } 47 | } 48 | ``` 49 | 50 | ### `tsconfigPath` 51 | 52 | Default: `./tsconfig.json` 53 | 54 | A relative path to your `tsconfig.json` file. 55 | 56 | ## run 57 | 58 | ``` 59 | jest -c jest.tsc.config.js 60 | ``` 61 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ 3 | { 4 | displayName: 'test', 5 | testMatch: ['/test/*.js'], 6 | }, 7 | { 8 | displayName: 'lint', 9 | runner: 'jest-runner-eslint', 10 | testMatch: [ 11 | '/src/*.js', 12 | '/test/*.js', 13 | '/*.js', 14 | ], 15 | testPathIgnorePatterns: ['node_modules'], 16 | }, 17 | { 18 | displayName: 'tsc', 19 | rootDir: './test', 20 | moduleFileExtensions: ['ts', 'js'], 21 | runner: '/../dist/index.js', 22 | testMatch: ['/*.ts'], 23 | testPathIgnorePatterns: ['lib-bad.test.ts'], 24 | }, 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-runner-tsc", 3 | "version": "0.0.0-development", 4 | "description": "A Jest Runner for the TypeScript compiler", 5 | "main": "dist", 6 | "engines": { 7 | "node": ">=8" 8 | }, 9 | "author": "Lucas Azzola <@azz>", 10 | "license": "MIT", 11 | "scripts": { 12 | "build": "babel src -d dist", 13 | "test": "yarn build && jest", 14 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 15 | }, 16 | "peerDependencies": { 17 | "jest": ">=22" 18 | }, 19 | "devDependencies": { 20 | "@babel/cli": "^7.2.3", 21 | "@babel/core": "^7.2.2", 22 | "@babel/preset-env": "^7.3.1", 23 | "@types/jest": "^23.3.13", 24 | "babel-core": "7.0.0-bridge.0", 25 | "eslint": "^5.12.1", 26 | "eslint-config-prettier": "^4.0.0", 27 | "eslint-plugin-jest": "^22.2.0", 28 | "eslint-plugin-prettier": "^3.0.1", 29 | "jest": "^24.0.0", 30 | "jest-runner-eslint": "^0.7.1", 31 | "prettier": "^1.16.1", 32 | "semantic-release": "^8.2.0", 33 | "typescript": "^3.2.4" 34 | }, 35 | "dependencies": { 36 | "@babel/code-frame": "^7.0.0", 37 | "cosmiconfig": "^5.2.1", 38 | "create-jest-runner": "~0.4.1" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/azz/jest-runner-tsc.git" 43 | }, 44 | "files": [ 45 | "/dist" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { createJestRunner } from 'create-jest-runner'; 2 | import cosmiconfig from 'cosmiconfig'; 3 | 4 | const explorer = cosmiconfig('jest-runner-tsc'); 5 | 6 | const getExtraOptions = () => { 7 | const searchedFor = explorer.searchSync(); 8 | if (!searchedFor || typeof searchedFor.config === 'undefined') { 9 | return {}; 10 | } 11 | 12 | return searchedFor.config; 13 | }; 14 | 15 | module.exports = createJestRunner(require.resolve('./runTsc'), { 16 | getExtraOptions, 17 | }); 18 | -------------------------------------------------------------------------------- /src/runTsc.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { codeFrameColumns as codeFrame } from '@babel/code-frame'; 3 | import ts from 'typescript'; 4 | import fs from 'fs'; 5 | import { pass, fail } from 'create-jest-runner'; 6 | 7 | const appendCodeFrame = ({ filePath, errorMessage, location }) => { 8 | if (typeof location === 'undefined') { 9 | return errorMessage; 10 | } 11 | const rawLines = fs.readFileSync(filePath, 'utf8'); 12 | return `${errorMessage}\n${codeFrame(rawLines, location, { 13 | highlightCode: true, 14 | })}`; 15 | }; 16 | 17 | const runTsc = ({ testPath, config: jestConfig, extraOptions }) => { 18 | const start = Date.now(); 19 | 20 | const configPath = 21 | typeof extraOptions.tsconfigPath === 'string' 22 | ? path.resolve(extraOptions.tsconfigPath) 23 | : path.resolve(jestConfig.rootDir, 'tsconfig.json'); 24 | 25 | if (!fs.existsSync(configPath)) { 26 | throw new Error( 27 | 'Cannot find tsconfig file. Either create one in the root of your project or define a custom path via the `tsconfigPath` option.' 28 | ); 29 | } 30 | 31 | const configContents = fs.readFileSync(configPath).toString(); 32 | const { config, error } = ts.parseConfigFileTextToJson( 33 | configPath, 34 | configContents 35 | ); 36 | 37 | const baseObj = { 38 | start, 39 | title: 'tsc', 40 | test: { path: testPath }, 41 | }; 42 | 43 | if (error) { 44 | return fail({ 45 | ...baseObj, 46 | end: Date.now(), 47 | errorMessage: error, 48 | }); 49 | } 50 | 51 | const settings = ts.convertCompilerOptionsFromJson( 52 | config['compilerOptions'] || {}, 53 | process.cwd() 54 | ); 55 | 56 | const options = Object.assign({}, { noEmit: true }, settings.options); 57 | 58 | const program = ts.createProgram([testPath], options); 59 | 60 | const emitResult = program.emit(); 61 | 62 | const allDiagnostics = ts 63 | .getPreEmitDiagnostics(program) 64 | .concat(emitResult.diagnostics) 65 | .filter(diagnostic => diagnostic.file.fileName === testPath); 66 | 67 | const errors = allDiagnostics 68 | .map(diagnostic => { 69 | if (diagnostic.file) { 70 | const { 71 | line: lineStart, 72 | character: characterStart, 73 | } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); 74 | const { 75 | line: lineEnd, 76 | character: characterEnd, 77 | } = diagnostic.file.getLineAndCharacterOfPosition( 78 | diagnostic.start + diagnostic.length 79 | ); 80 | 81 | const location = { 82 | start: { 83 | line: lineStart + 1, 84 | column: characterStart + 1, 85 | }, 86 | end: { 87 | line: lineEnd + 1, 88 | column: characterEnd + 1, 89 | }, 90 | }; 91 | 92 | const message = ts.flattenDiagnosticMessageText( 93 | diagnostic.messageText, 94 | '\n' 95 | ); 96 | 97 | return { 98 | location, 99 | errorMessage: message, 100 | filePath: diagnostic.file.fileName, 101 | }; 102 | } else { 103 | return { 104 | errorMessage: `${ts.flattenDiagnosticMessageText( 105 | diagnostic.messageText, 106 | '\n' 107 | )}`, 108 | filePath: testPath, 109 | }; 110 | } 111 | }) 112 | .map(appendCodeFrame); 113 | 114 | const end = Date.now(); 115 | 116 | if (errors.length === 0) { 117 | return pass({ ...baseObj, end }); 118 | } 119 | 120 | return fail({ 121 | ...baseObj, 122 | errorMessage: errors.join('\n\n'), 123 | end, 124 | }); 125 | }; 126 | 127 | module.exports = runTsc; 128 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | describe('runner', () => { 2 | test('passes', () => { 3 | const alwaysTrue = true; 4 | expect(alwaysTrue).toBeTruthy(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /test/lib-bad.test.ts: -------------------------------------------------------------------------------- 1 | const libTestBad = (): string => { 2 | return 42; 3 | }; 4 | -------------------------------------------------------------------------------- /test/lib-good.test.ts: -------------------------------------------------------------------------------- 1 | const libTestGood = (): string => { 2 | return 'apples'; 3 | }; 4 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es2018", 5 | "dom" 6 | ], 7 | "target": "es5", 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "strict": true 11 | }, 12 | "include": [ 13 | "lib.test.ts" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------