diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..1385f5ff --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_style = spaces +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..08b25532 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..121dfa3b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License + +Copyright (C) 2016 Erik Barke. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..12e7e512 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# karma-typescript +This is a preprocessor for transpiling Typescript on the fly for unit testing with Karma. + +### Sourcemaps +Sourcemaps can optionally be created for debugging the original Typescript code, instead of the transpiled code, when running tests in a web browser. + +### Project tsconfig.json +If specified, the preprocessor will load a tsconfig.json file, relative to the directory the test are run from, and merge the compiler options with the preprocessor options. + +Note: the preprocessor options will override the tsconfig.json options. + +### Npm installation: +`npm install karma-typescript --save-dev` + +### Configuration + +The `karmaTypescript` configuration section is optional, if left out the files will be transpiled using the compiler default options. + +```javascript +preprocessors: { + '**/*.ts': ['karma-typescript'] +}, + +karmaTypescript: { + + /* + Relative path to a tsconfig.json file, the + preprocessor will look for the file starting + from the directory where Karma was started. + This property is optional. + */ + tsconfigPath: 'tsconfig.json', + + /* + A function for custom file path transformation. + This property is optional. + */ + transformPath: function(filepath) { + return filepath.replace(/\.ts$/, '.js') + }, + + /* + These are options for the Typescript compiler. + They will override the options in the tsconfig.json + file specified in tsconfigPath above. + This property is optional. + */ + options: { + sourceMap: true, + target: 'es5', + module: 'commonjs' + // ... More options are available at: + // https://www.typescriptlang.org/docs/handbook/compiler-options.html + } +} +``` + +### Example project using jasmine and commonjs + sourcemaps: + +There is a working example in the folder `example-project`. + +In the example, the .ts files are transpiled to `es5` format with sourcemaps, and then run through the karma-commonjs preprocessor for module loading. + +To run the example test, issue the following commands: + +``` +cd example-project +npm install +npm test +``` + +This should run the example unit test in Chrome, with sourcemaps available if you press the button `debug` in the browser window. + +### Example breakdown +The unit test showcases module loading by using `import` but is still as simple as it gets; it imports the class to be tested and an interface required by the class. The interface is mocked, the class is instantiated and then the method `sayHello` is tested: + +```javascript +import { IHelloService } from './hello-service.interface'; +import { HelloComponent } from './hello.component'; + +class MockHelloService implements IHelloService { + + public sayHello(): string { + return 'hello world!'; + } +} + +describe('HelloComponent', () => { + + it('should', () => { + + let mockHelloService = new MockHelloService(); + let helloComponent = new HelloComponent(mockHelloService); + + expect(helloComponent.sayHello()).toEqual('hello world!'); + }); +}); +``` +Essential parts of karma.conf.js: + +```javascript +frameworks: ['jasmine', 'commonjs'], + +files: [ + { pattern: 'src/**/*.ts' } +], + +preprocessors: { + '**/*.ts': ['karma-typescript', 'commonjs'] +}, + +karmaTypescript: { + tsconfigPath: 'tsconfig.json', + options: { + sourceMap: true, + target: 'es5', + module: 'commonjs' + } +} +``` +If you inspect the tsconfig.json file you will see that the sourceMap option in karma.conf.js overrides the sourceMap option in the tsconfig.json. + +### Licensing + +This software is licensed with the MIT license. + +© 2016 Monounity diff --git a/example-project/.npmignore b/example-project/.npmignore new file mode 100644 index 00000000..08b25532 --- /dev/null +++ b/example-project/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/example-project/README.md b/example-project/README.md new file mode 100644 index 00000000..c1ca7256 --- /dev/null +++ b/example-project/README.md @@ -0,0 +1,7 @@ +# karma-typescript-example-project + +## Licensing + +This software is licensed with the MIT license. + +© 2016 Monounity diff --git a/example-project/karma.conf.js b/example-project/karma.conf.js new file mode 100644 index 00000000..41ba4445 --- /dev/null +++ b/example-project/karma.conf.js @@ -0,0 +1,25 @@ +module.exports = function(config) { + config.set({ + + frameworks: ['jasmine', 'commonjs'], + + files: [ + { pattern: 'src/**/*.ts' } + ], + + preprocessors: { + '**/*.ts': ['karma-typescript', 'commonjs'] + }, + + karmaTypescript: { + tsconfigPath: 'tsconfig.json', + options: { + sourceMap: true, + target: 'es5', + module: 'commonjs' + } + }, + + browsers: ['Chrome'] + }) +} diff --git a/example-project/package.json b/example-project/package.json new file mode 100644 index 00000000..6de06ba4 --- /dev/null +++ b/example-project/package.json @@ -0,0 +1,27 @@ +{ + "name": "karma-typescript-example-project", + "version": "1.0.0", + "description": "", + "author": "monounity", + "contributors": [ + { + "name": "erikbarke" + } + ], + "private": true, + "license": "MIT", + "scripts": { + "test": "karma start" + }, + "devDependencies": { + "@types/jasmine": "^2.2.31", + "jasmine-core": "^2.4.1", + "karma": "^1.2.0", + "karma-chrome-launcher": "^2.0.0", + "karma-cli": "^1.0.1", + "karma-commonjs": "^1.0.0", + "karma-jasmine": "^1.0.2", + "karma-typescript": "^1.0.3", + "typescript": "^2.0.0" + } +} diff --git a/example-project/src/hello-component.spec.ts b/example-project/src/hello-component.spec.ts new file mode 100644 index 00000000..2550fd62 --- /dev/null +++ b/example-project/src/hello-component.spec.ts @@ -0,0 +1,20 @@ +import { IHelloService } from './hello-service.interface'; +import { HelloComponent } from './hello.component'; + +class MockHelloService implements IHelloService { + + public sayHello(): string { + return 'hello world!'; + } +} + +describe('HelloComponent', () => { + + it('should', () => { + + let mockHelloService = new MockHelloService(); + let helloComponent = new HelloComponent(mockHelloService); + + expect(helloComponent.sayHello()).toEqual('hello world!'); + }); +}); diff --git a/example-project/src/hello-service.interface.ts b/example-project/src/hello-service.interface.ts new file mode 100644 index 00000000..332ae06a --- /dev/null +++ b/example-project/src/hello-service.interface.ts @@ -0,0 +1,3 @@ +export interface IHelloService { + sayHello(): string; +} diff --git a/example-project/src/hello.component.ts b/example-project/src/hello.component.ts new file mode 100644 index 00000000..8d34c629 --- /dev/null +++ b/example-project/src/hello.component.ts @@ -0,0 +1,11 @@ +import { IHelloService } from './hello-service.interface'; + +export class HelloComponent { + + constructor(private helloService: IHelloService) {} + + public sayHello(): string { + + return this.helloService.sayHello(); + } +} diff --git a/example-project/tsconfig.json b/example-project/tsconfig.json new file mode 100644 index 00000000..5d11730e --- /dev/null +++ b/example-project/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "sourceMap": false, + "types":["jasmine"] + }, + "exclude": [ + "node_modules" + ] +} diff --git a/index.js b/index.js new file mode 100644 index 00000000..82db1c5f --- /dev/null +++ b/index.js @@ -0,0 +1,85 @@ +var fs = require('fs'); +var path = require('path'); + +var createTypescriptPreprocessor = function(args, config, logger, helper) { + + config = config || {}; + + var tsconfigPath; + var tsconfig; + var compilerOptions; + var log = logger.create('preprocessor.karma-typescript'); + var tsc = require('typescript'); + + var transformPath = args.transformPath || config.transformPath || function (filepath) { + return filepath.replace(/\.ts$/, '.js') + } + + log.info('Using Typescript %s', tsc.version); + log.debug('Compiler options from karma configuration:\n%s', JSON.stringify(config.options, null, 4)); + + if (config.tsconfigPath) { + + try { + + tsconfigPath = process.cwd() + '/' + config.tsconfigPath; + tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8')); + + log.info('Using tsconfig from %s', tsconfigPath); + log.debug('Compiler options from %s:\n%s', tsconfigPath, JSON.stringify(tsconfig.compilerOptions, null, 4)); + } + catch(error) { + + log.warn(error.message); + } + } + + if (tsconfig) { + + compilerOptions = helper.merge(tsconfig.compilerOptions || {}, config.options || {}); + log.debug('Compiler options after merge with karma configuration and tsconfig:\n%s', JSON.stringify(compilerOptions, null, 4)); + } + + return function(content, file, done) { + + var transpileOutput, + reportDiagnostics = false, + moduleName = path.relative(process.cwd(), file.originalPath), + map; + + log.debug('Processing "%s".', file.originalPath); + + try { + + file.path = transformPath(file.originalPath); + + transpileOutput = tsc.transpileModule(content, { compilerOptions: compilerOptions }, reportDiagnostics, moduleName); + + if(transpileOutput.sourceMapText) { + + map = JSON.parse(transpileOutput.sourceMapText); + map.sources[0] = path.basename(file.originalPath); + map.sourcesContent = [content]; + map.file = path.basename(file.path); + file.sourceMap = map; + datauri = 'data:application/json;charset=utf-8;base64,' + new Buffer(JSON.stringify(map)).toString('base64'); + + done(null, transpileOutput.outputText + '\n//# sourceMappingURL=' + datauri + '\n') + } + else { + done(null, transpileOutput.outputText); + } + } + catch(e) { + + log.error('%s\n at %s\n%s', e.message, file.originalPath, e.stack); + return done(e, null); + } + }; +}; + +createTypescriptPreprocessor.$inject = ['args', 'config.karmaTypescript', 'logger', 'helper']; + +module.exports = { + 'preprocessor:karma-typescript': ['factory', createTypescriptPreprocessor] +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..ce7c161f --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "karma-typescript", + "version": "1.0.8", + "description": "Preprocessor for transpiling Typescript files on the fly with sourcemaps for debugging.", + "main": "index.js", + "keywords": [ + "Jasmine", + "CommonJS", + "Karma", + "Typescript", + "Unit test" + ], + "author": "monounity", + "contributors": [ + { + "name": "erikbarke" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/monounity/karma-typescript.git" + }, + "license": "MIT" +}