├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── demo ├── ava-1-test.js └── ava-2-test.js ├── index.js ├── karma.conf.js ├── lib ├── ava-bundler.js ├── babel-transform.js ├── browser-adapter.js ├── cache-middleware.js ├── child-process-middleware.js ├── externalize-bundle.js ├── main.js ├── preprocessor-main.js ├── preprocessor.js ├── promisify-bundle.js ├── replace-transform.js └── test-bundler.js ├── license ├── package.json ├── readme.md └── test ├── _utils.js ├── babel-transform.js ├── cache-middleware.js ├── child-process-middleware.js ├── fixtures ├── foo.js └── test │ ├── _es2015.js │ ├── es2015.js │ ├── foo.js │ └── has-syntax-errors.js └── test-bundler.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | temp 5 | /main.js 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '5' 5 | - '4' 6 | - '0.12' 7 | - '0.10' 8 | -------------------------------------------------------------------------------- /demo/ava-1-test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; // eslint-disable-line ava/no-ignored-test-files 2 | 3 | test('foo', t => { 4 | t.is(1, 1); 5 | }); 6 | 7 | test('bar', t => { 8 | t.is(1, 1); 9 | }); 10 | -------------------------------------------------------------------------------- /demo/ava-2-test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; // eslint-disable-line ava/no-ignored-test-files 2 | 3 | test('foo', t => { 4 | t.is(1, 1); 5 | }); 6 | 7 | test('bar', t => { 8 | var a = {foo: 'foo'}; 9 | var b = {bar: 'bar'}; 10 | t.is(a.foo, b.bar); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var express = require('express'); 4 | var cacheMiddleware = require('./lib/cache-middleware'); 5 | var childProcessMiddleware = require('./lib/child-process-middleware'); 6 | 7 | function createPattern(path) { 8 | return { 9 | pattern: path, 10 | included: true, 11 | served: true, 12 | watched: false 13 | }; 14 | } 15 | 16 | function framework(config) { 17 | var mainPath = path.join(__dirname, 'lib', 'main.js'); 18 | 19 | config.preprocessors = config.preprocessors || {}; 20 | 21 | config.preprocessors[mainPath] = ['ava-main']; 22 | 23 | config.files.forEach(function (file) { 24 | config.preprocessors[file.pattern] = ['ava']; 25 | }); 26 | 27 | config.files.unshift( 28 | createPattern(mainPath) 29 | ); 30 | 31 | // This will not work until `karma@1.1.0` - users should manually add `ava` until then. 32 | config.middleware = config.middleware || []; 33 | 34 | if (config.middleware.indexOf('ava') === -1) { 35 | config.middleware.push('ava'); 36 | } 37 | } 38 | 39 | framework.$inject = ['config']; 40 | 41 | function middlewareFactory(cwd) { 42 | return express() 43 | .use('/karma-ava/', cacheMiddleware('karma-ava', cwd)) 44 | .use('/karma-ava/child/', childProcessMiddleware); 45 | } 46 | 47 | middlewareFactory.$inject = ['config.basedir']; 48 | 49 | module.exports = { 50 | 'ava-test-bundler': ['factory', require('./lib/test-bundler')], 51 | 'framework:ava': ['factory', framework], 52 | 'preprocessor:ava': ['factory', require('./lib/preprocessor')], 53 | 'preprocessor:ava-main': ['factory', require('./lib/preprocessor-main')], 54 | 'middleware:ava': ['factory', middlewareFactory] 55 | }; 56 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ['ava'], 4 | basePath: 'demo', 5 | files: ['ava-*-test.js'], 6 | browsers: [ 7 | 'Chrome' 8 | ], 9 | plugins: [ 10 | require('karma-chrome-launcher'), 11 | require('./') 12 | ], 13 | singleRun: false, 14 | autoRun: true 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/ava-bundler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var browserify = require('browserify'); 3 | var resolveFrom = require('resolve-from'); 4 | var replaceTransform = require('./replace-transform'); 5 | 6 | module.exports = function (cwd, debug, browserAdapterPath) { 7 | var avaBundler = browserify({debug: debug, basedir: cwd, fullPaths: true}); 8 | var processAdapterPath = resolveFrom(cwd, 'ava/lib/process-adapter'); 9 | var avaPath = resolveFrom(cwd, 'ava'); 10 | avaBundler.avaProcessAdapterPath = processAdapterPath; 11 | 12 | avaBundler.require(processAdapterPath); 13 | 14 | avaBundler.require(avaPath); 15 | 16 | avaBundler.transform(replaceTransform(processAdapterPath, browserAdapterPath), {global: true}); 17 | 18 | return avaBundler; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/babel-transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var stream = require('stream'); 3 | var util = require('util'); 4 | var babel = require('babel-core'); 5 | var babelConfigBuilder = require('ava/lib/babel-config'); 6 | 7 | function makeTransform(opts) { 8 | var babelConfig = babelConfigBuilder.validate(opts.babel); 9 | 10 | return function (filename) { 11 | if (opts.file !== filename) { 12 | return new stream.PassThrough(); 13 | } 14 | 15 | return new Babelify(filename, babelConfig); 16 | }; 17 | } 18 | 19 | module.exports = makeTransform; 20 | 21 | // copied from Babelify - modified so we can embed source maps 22 | function Babelify(filename, babelConfig) { 23 | stream.Transform.call(this); 24 | this._data = ''; 25 | this._filename = filename; 26 | this._babelConfig = babelConfig; 27 | } 28 | 29 | util.inherits(Babelify, stream.Transform); 30 | 31 | Babelify.prototype._transform = function (buf, enc, callback) { 32 | this._data += buf; 33 | callback(); 34 | }; 35 | 36 | Babelify.prototype._flush = function (callback) { 37 | try { 38 | var babelOpts = babelConfigBuilder.build(this._babelConfig, this._filename, this._data); 39 | babelOpts.sourceMaps = 'inline'; 40 | var result = babel.transform(this._data, babelOpts); 41 | this.emit('babelify', result, this._filename); 42 | var code = result.code; 43 | this.push(code); 44 | } catch (err) { 45 | this.emit('error', err); 46 | return; 47 | } 48 | 49 | callback(); 50 | }; 51 | -------------------------------------------------------------------------------- /lib/browser-adapter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var EventEmitter = require('events').EventEmitter; 3 | var globals = require('ava/lib/globals'); 4 | 5 | // Chrome gets upset when the `this` value is non-null for these functions. 6 | globals.setTimeout = setTimeout.bind(null); 7 | globals.clearTimeout = clearTimeout.bind(null); 8 | 9 | var process = new EventEmitter(); 10 | 11 | window.addEventListener('message', function (event) { 12 | if (event.source !== window) { 13 | event = event.data; 14 | process.emit(event.name, event.data); 15 | } 16 | }); 17 | 18 | // Mock the behavior of a parent process. 19 | process.send = function (name, data) { 20 | window.postMessage({ 21 | name: 'ava-' + name, 22 | data: data, 23 | ava: true 24 | }, '*'); 25 | }; 26 | 27 | process.env = {}; 28 | process.opts = { 29 | file: 'test-file' 30 | }; 31 | 32 | process.installSourceMapSupport = function () {}; 33 | process.installPrecompilerHook = function () {}; 34 | process.installDependencyTracking = function () {}; 35 | process.exit = function () {}; 36 | 37 | module.exports = process; 38 | 39 | setTimeout(function () { 40 | require('ava/lib/test-worker'); 41 | }); 42 | -------------------------------------------------------------------------------- /lib/cache-middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var findCacheDir = require('find-cache-dir'); 3 | var express = require('express'); 4 | 5 | module.exports = function (cacheName, cwd) { 6 | var cacheDir = findCacheDir({name: cacheName, cwd: cwd}); 7 | return express.static(cacheDir); 8 | }; 9 | -------------------------------------------------------------------------------- /lib/child-process-middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var express = require('express'); 3 | 4 | function middleware(req, res) { 5 | res.set('Content-Type', 'text/html'); 6 | res.send([ 7 | '', 8 | ' ', 9 | ' ', 10 | ' ', 11 | ' ', 12 | '' 13 | ].join('\n')); 14 | } 15 | 16 | module.exports = express().get('/:hash1/:hash2', middleware); 17 | -------------------------------------------------------------------------------- /lib/externalize-bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var builtins = require('browserify/lib/builtins'); 3 | var browserResolve = require('browser-resolve'); 4 | 5 | // Adapted from browserify.prototype.external. 6 | // This could be made much simpler with some help from browserify. 7 | // See: https://github.com/substack/node-browserify/pull/1577 8 | module.exports = function (bundler) { 9 | var externalIds = []; 10 | 11 | bundler.on('label', function (prev, id) { 12 | externalIds.push(prev); 13 | 14 | if (prev !== id) { 15 | externalIds.push(id); 16 | } 17 | }); 18 | 19 | function rewriteIndex(row) { 20 | Object.keys(row.deps).forEach(function (key) { 21 | var resolved = browserResolve.sync(key, { 22 | filename: row.file, 23 | modules: builtins 24 | }); 25 | 26 | row.deps[key] = resolved; 27 | 28 | if (row.indexDeps) { 29 | row.indexDeps[key] = resolved; 30 | } 31 | }); 32 | } 33 | 34 | return function externalize(otherBundler) { 35 | externalIds.forEach(function (id) { 36 | otherBundler._external.push(id); 37 | }); 38 | 39 | otherBundler.on('dep', rewriteIndex); 40 | 41 | return otherBundler; 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var EventEmitter = require('events').EventEmitter; 3 | var Promise = require('bluebird'); 4 | 5 | var tests = []; 6 | 7 | function addFile(avaHash, fileHash, prefix) { 8 | var ee = new EventEmitter(); 9 | 10 | var iframe = document.createElement('iframe'); 11 | iframe.style.display = 'none'; 12 | iframe.src = '/karma-ava/child/' + avaHash + '/' + fileHash; 13 | document.body.appendChild(iframe); 14 | var subWindow = iframe.contentWindow; 15 | 16 | ee.send = function (name, data) { 17 | subWindow.postMessage({ 18 | name: 'ava-' + name, 19 | data: data, 20 | ava: true 21 | }, '*'); 22 | }; 23 | 24 | function listener(event) { 25 | if (event.source !== subWindow || !event.data.ava) { 26 | return; 27 | } 28 | 29 | event = event.data; 30 | 31 | var name = event.name.replace(/^ava\-/, ''); 32 | 33 | ee.emit(name, event.data); 34 | } 35 | 36 | subWindow.addEventListener('message', listener, false); 37 | 38 | var started = false; 39 | 40 | var statsPromise = new Promise(function (resolve) { 41 | ee.once('stats', resolve); 42 | }); 43 | 44 | var resultsPromise = new Promise(function (resolve) { 45 | ee.once('results', resolve); 46 | }); 47 | 48 | resultsPromise.then(function () { 49 | subWindow.removeEventListener('message', listener, false); 50 | document.body.removeChild(iframe); 51 | }); 52 | 53 | function start(opts) { 54 | if (started) { 55 | return; 56 | } 57 | 58 | started = true; 59 | 60 | statsPromise.then(function () { 61 | // TODO: Handle default opts in AVA 62 | ee.send('run', opts || {}); 63 | }); 64 | } 65 | 66 | // TODO: uncaughtExceptions and unhandledRejections 67 | ee.on('test', function (test) { 68 | window.__karma__.result({ 69 | id: '', 70 | description: prefix + test.title, 71 | suite: [], 72 | log: [test.error && test.error.message], 73 | success: !test.error, 74 | skipped: test.skip || test.todo 75 | }); 76 | }); 77 | 78 | tests.push({ 79 | start: start, 80 | stats: statsPromise, 81 | results: resultsPromise 82 | }); 83 | } 84 | 85 | window.__AVA__ = { 86 | addFile: addFile 87 | }; 88 | 89 | window.__karma__.start = function start() { 90 | Promise.map(tests, function (test) { 91 | return test.stats; 92 | }).then(function (stats) { 93 | window.__karma__.info({ 94 | total: stats.reduce(function (previous, stats) { 95 | return previous + stats.testCount; 96 | }, 0) 97 | }); 98 | 99 | tests.forEach(function (test) { 100 | test.start(); 101 | }); 102 | }); 103 | 104 | Promise.map(tests, function (test) { 105 | return test.results; 106 | }).then(function () { 107 | window.__karma__.complete({}); 108 | }); 109 | }; 110 | -------------------------------------------------------------------------------- /lib/preprocessor-main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var browserify = require('browserify'); 4 | 5 | var mainPath = path.join(__dirname, 'main.js'); 6 | 7 | // This is a convenience while things are still under active development. 8 | // TODO: main.js should be built and shipped as part of the publish process 9 | function factory() { 10 | return function (content, file, done) { 11 | if (file.originalPath !== mainPath) { 12 | done(null, content); 13 | return; 14 | } 15 | 16 | var fileCache = {}; 17 | fileCache[mainPath] = content; 18 | 19 | var bundler = browserify({debug: true, basedir: __dirname, fileCache: fileCache}); 20 | bundler.add(mainPath, {basedir: __dirname}); 21 | bundler.bundle(done); 22 | }; 23 | } 24 | 25 | factory.$inject = []; 26 | 27 | module.exports = factory; 28 | -------------------------------------------------------------------------------- /lib/preprocessor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var pify = require('pify'); 5 | var Promise = require('bluebird'); 6 | var findCacheDir = require('find-cache-dir'); 7 | var md5hex = require('md5-hex'); 8 | var figures = require('figures'); 9 | var prefixTitle = require('ava/lib/prefix-title'); 10 | 11 | var fsP = pify(fs, Promise); 12 | var separator = ' ' + figures.pointerSmall + ' '; 13 | 14 | var api = ['config.basePath', 'ava-test-bundler']; 15 | function preprocessorFactory(cwd, bundler) { 16 | var cacheDir = findCacheDir({ 17 | name: 'karma-ava', 18 | cwd: cwd, 19 | create: true, 20 | thunk: true 21 | }); 22 | 23 | function hashAndCache(bundleSource) { 24 | var hash = md5hex(bundleSource); 25 | return fsP.writeFile(cacheDir(hash + '.js'), bundleSource).thenReturn(hash); 26 | } 27 | 28 | var ava = bundler.ava.then(hashAndCache); 29 | 30 | return function avaPreprocessor(content, file, done) { 31 | var prefix = prefixTitle(file.originalPath, cwd + path.sep, separator); 32 | 33 | ava.then(function (avaHash) { 34 | bundler.bundleFile(file.originalPath, content) 35 | .then(bundler.appendRequire) 36 | .then(hashAndCache) 37 | .then(function (fileHash) { 38 | done(null, 'window.__AVA__.addFile("' + avaHash + '", "' + fileHash + '", "' + prefix + '");\n'); 39 | }) 40 | .catch(function (err) { 41 | // TODO: Should fail the build 42 | console.log(err); 43 | done(null, ' '); 44 | }); 45 | }); 46 | }; 47 | } 48 | 49 | preprocessorFactory.$inject = api; 50 | 51 | module.exports = preprocessorFactory; 52 | -------------------------------------------------------------------------------- /lib/promisify-bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Promise = require('bluebird'); 3 | var pify = require('pify'); 4 | 5 | module.exports = function (bundler) { 6 | var promise = pify(bundler.bundle.bind(bundler), Promise)(); 7 | promise.on = bundler.on.bind(bundler); 8 | return promise; 9 | }; 10 | -------------------------------------------------------------------------------- /lib/replace-transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var stream = require('stream'); 4 | var pify = require('pify'); 5 | var Promise = require('bluebird'); 6 | 7 | // Replace the contents of one file with another. 8 | // Like the browserify `browser` in package.json field, but can be used to replace things inside `node_modules`. 9 | // This should be the first transform in the chain. 10 | module.exports = function (from, to) { 11 | return function (filename) { 12 | if (filename !== from) { 13 | return new stream.PassThrough(); 14 | } 15 | 16 | var transform = new stream.Transform(); 17 | var replacement = pify(fs, Promise).readFile(to, 'utf8'); 18 | 19 | transform._transform = function (buf, enc, cb) { 20 | // ignore incoming file 21 | cb(); 22 | }; 23 | 24 | transform._flush = function (cb) { 25 | replacement.then(function (replacement) { 26 | transform.push(replacement); 27 | cb(); 28 | }).catch(cb); 29 | }; 30 | 31 | return transform; 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/test-bundler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var browserify = require('browserify'); 4 | var externalizeBundle = require('./externalize-bundle'); 5 | var getBundlePromise = require('./promisify-bundle'); 6 | var babelTransform = require('./babel-transform'); 7 | var makeAvaBundler = require('./ava-bundler'); 8 | 9 | function bundlerFactory(cwd) { 10 | var browserAdapterPath = path.join(__dirname, 'browser-adapter.js'); 11 | var avaBundler = makeAvaBundler(cwd, true, browserAdapterPath); 12 | 13 | var externalizeAva = externalizeBundle(avaBundler); 14 | var avaP = getBundlePromise(avaBundler); 15 | 16 | function bundleFile(file, src) { 17 | return avaP.then(function () { 18 | var fileCache = {}; 19 | 20 | if (src) { 21 | fileCache[file] = src; 22 | } 23 | 24 | var bundler = browserify({debug: true, basedir: cwd, fileCache: fileCache, fullPaths: true}); 25 | 26 | bundler.transform(babelTransform({ 27 | cwd: cwd, 28 | file: file 29 | })); 30 | 31 | externalizeAva(bundler); 32 | 33 | bundler.require(file, {expose: 'test-file', basedir: cwd}); 34 | 35 | return getBundlePromise(bundler); 36 | }); 37 | } 38 | 39 | var requireCode = ';\nrequire("' + avaBundler.avaProcessAdapterPath + '");'; 40 | 41 | function appendRequire(code) { 42 | return code + requireCode; 43 | } 44 | 45 | return { 46 | ava: avaP, 47 | bundleFile: bundleFile, 48 | requireCode: requireCode, 49 | appendRequire: appendRequire 50 | }; 51 | } 52 | 53 | bundlerFactory.$inject = ['config.basePath']; 54 | 55 | module.exports = bundlerFactory; 56 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) James Talmage (github.com/jamestalmage) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "karma-ava", 3 | "version": "0.0.1", 4 | "description": "Karma plugin for AVA", 5 | "license": "MIT", 6 | "repository": "avajs/karma-ava", 7 | "author": { 8 | "name": "James Talmage", 9 | "email": "james@talmage.io", 10 | "url": "github.com/jamestalmage" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "xo && nyc ava", 17 | "demo": "karma start" 18 | }, 19 | "files": [ 20 | "index.js", 21 | "lib" 22 | ], 23 | "keywords": [ 24 | "karma", 25 | "ava", 26 | "plugin", 27 | "browser", 28 | "test", 29 | "testing" 30 | ], 31 | "dependencies": { 32 | "babel-core": "^6.9.1", 33 | "bluebird": "^3.4.1", 34 | "browser-resolve": "^1.11.2", 35 | "browserify": "^13.0.1", 36 | "express": "^4.14.0", 37 | "figures": "^1.7.0", 38 | "find-cache-dir": "^0.1.1", 39 | "md5-hex": "^1.3.0", 40 | "object-assign": "^4.1.0", 41 | "pify": "^2.3.0", 42 | "resolve-from": "^2.0.0", 43 | "through2": "^2.0.1" 44 | }, 45 | "devDependencies": { 46 | "ava": "github:jamestalmage/ava#karma-ava", 47 | "browser-unpack": "^1.1.1", 48 | "karma": "^1.1.0", 49 | "karma-chrome-launcher": "^1.0.1", 50 | "nyc": "^6.6.1", 51 | "supertest": "^1.2.0", 52 | "supertest-as-promised": "^3.1.0", 53 | "xo": "^0.16.0" 54 | }, 55 | "xo": { 56 | "envs": [ 57 | "browser", 58 | "node" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # karma-ava [![Build Status](https://travis-ci.org/avajs/karma-ava.svg?branch=master)](https://travis-ci.org/avajs/karma-ava) 2 | 3 | > Run [AVA](https://ava.li) tests with [Karma](https://karma-runner.github.io) 4 | 5 | *WARNING: Alpha level software - for evaluation use only* 6 | 7 | 8 | ## Install 9 | 10 | *Note: this currently requires a custom build of AVA* 11 | 12 | ``` 13 | $ npm install --save-dev jamestalmage/ava#karma-ava karma karma-ava karma-chrome-launcher 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | Create a `karma.conf.js`, like so: 20 | 21 | ```js 22 | module.exports = config => { 23 | config.set({ 24 | frameworks: ['ava'], 25 | files: [ 26 | 'test/*.js' 27 | ], 28 | browsers: ['Chrome'] 29 | }); 30 | }; 31 | ``` 32 | 33 | Then run `karma start`: 34 | 35 | ``` 36 | $ node_modules/.bin/karma start 37 | ``` 38 | 39 | 40 | ## Notes 41 | 42 | - Be careful not to include test helpers in the `files` pattern (future improvements will automatically filter helpers out). 43 | 44 | 45 | ## How it works 46 | 47 | 1. The `ava` preprocessor (`lib/preprocessor.js`) bundles up a single test. Instead of returning the bundle result. It stores it at `node_modules/.cache/karma-ava/.js`. Karma sees a one-liner function call as the result: 48 | 49 | ```js 50 | window.__AVA__.addFile(AVA_HASH, TEST_HASH, TEST_PREFIX); 51 | ``` 52 | 53 | - `AVA_HASH` is the cache key for the external bundle of AVA common to all tests. It just contains AVA and its dependencies. 54 | 55 | - `TEST_HASH` is the cache key for the individual test bundle. 56 | 57 | - `TEST_PREFIX` is a string prefix to put before the test title, something like: `"dirname > filename > "`. 58 | 59 | 2. The `ava` middleware provides two routes: 60 | 61 | - `/karma-ava/.js` - returns the the bundle stored for that cache key (could be individual test bundles or the common AVA bundle). 62 | 63 | - `/karma-ava/child/:avaHash/:testHash` - returns an `html` page that simply loads two bundles (the common bundle, and the individual test bundle). These pages will be loaded into `iframes` by the main process. 64 | 65 | 3. `lib/main.js` 66 | 67 | - This is loaded in the main window by the framework. It provides the `window.__AVA__.addFile()` method discussed above, and acts as a test runner for individual `iframes`. It also communicates test results back to the Karma server in a format it understands. 68 | 69 | 70 | ## TODO 71 | 72 | - [ ] Create opinionated, configuration-free defaults that follow AVA's current style. 73 | - [ ] Give the user greater control over what goes in the external/common bundle. 74 | - [ ] Automatically ignore test helpers. 75 | - [ ] Create a custom Karma reporter that uses AVA's own loggers. 76 | - [ ] Honor `ava` config params from `package.json`, 77 | - [ ] Allow for custom `babel` configs, 78 | - [ ] Use `watchify` for faster rebuilds and smart, dependency-based rerun behavior. 79 | 80 | 81 | ## License 82 | 83 | MIT © [James Talmage](https://github.com/jamestalmage) 84 | -------------------------------------------------------------------------------- /test/_utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var slice = Array.prototype.slice; 4 | 5 | module.exports.fixture = function () { 6 | var args = slice.call(arguments); 7 | args.unshift(__dirname, 'fixtures'); 8 | 9 | return path.resolve.apply(path, args); 10 | }; 11 | -------------------------------------------------------------------------------- /test/babel-transform.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import browserify from 'browserify'; 3 | import pify from 'pify'; 4 | import babelTransform from '../lib/babel-transform'; 5 | import {fixture} from './_utils'; 6 | 7 | test('successfully converts es2015 code', async () => { 8 | const b = browserify(); 9 | b.transform(babelTransform({file: fixture('test', 'es2015.js')})); 10 | b.external('ava-bundler-external/process-adapter'); 11 | b.add(fixture('test', 'es2015.js')); 12 | await pify(b.bundle.bind(b), Promise)(); 13 | }); 14 | 15 | test('dows not convert if file does not match', t => { 16 | const b = browserify(); 17 | b.transform(babelTransform({file: fixture('test', 'es2015.js')})); 18 | b.external('ava-bundler-external/process-adapter'); 19 | b.add(fixture('test', '_es2015.js')); 20 | t.throws(pify(b.bundle.bind(b), Promise)()); 21 | }); 22 | -------------------------------------------------------------------------------- /test/cache-middleware.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import test from 'ava'; 3 | import request from 'supertest-as-promised'; 4 | import findCacheDir from 'find-cache-dir'; 5 | import pify from 'pify'; 6 | import cacheMiddleware from '../lib/cache-middleware'; 7 | 8 | const fsP = pify(fs, Promise); 9 | 10 | const time = () => process.hrtime().join('-'); 11 | 12 | async function macro(t, name) { 13 | const cacheDir = findCacheDir({name, create: true, thunk: true}); 14 | const filename = time() + '.txt'; 15 | 16 | await fsP.writeFile(cacheDir(filename), name); 17 | 18 | const res = await request(cacheMiddleware(name, __dirname)).get(`/${filename}`); 19 | 20 | t.is(res.status, 200); 21 | t.is(res.text, name); 22 | } 23 | 24 | macro.title = name => name; 25 | 26 | test(macro, 'foo'); 27 | test(macro, 'bar'); 28 | -------------------------------------------------------------------------------- /test/child-process-middleware.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import request from 'supertest-as-promised'; 3 | import childProcessMiddleware from '../lib/child-process-middleware'; 4 | 5 | async function macro(t, a, b) { 6 | const res = await request(childProcessMiddleware) 7 | .get(`/${a}/${b}`); 8 | 9 | t.is(res.status, 200); 10 | t.true(res.text.indexOf(`src="/karma-ava/${a}.js"`) > 0); 11 | t.true(res.text.indexOf(`src="/karma-ava/${b}.js"`) > 0); 12 | } 13 | 14 | macro.title = name => name; 15 | 16 | test(macro, 'foo', 'bar'); 17 | test(macro, 'baz', 'quz'); 18 | -------------------------------------------------------------------------------- /test/fixtures/foo.js: -------------------------------------------------------------------------------- 1 | module.exports = 'foo'; 2 | -------------------------------------------------------------------------------- /test/fixtures/test/_es2015.js: -------------------------------------------------------------------------------- 1 | import bluebird from 'bluebird'; 2 | import test from 'ava'; 3 | import foo from '../foo'; 4 | 5 | test(function (t) { 6 | t.is(foo, 'foo'); 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/test/es2015.js: -------------------------------------------------------------------------------- 1 | import bluebird from 'bluebird'; 2 | import test from 'ava'; 3 | import foo from '../foo'; 4 | 5 | test(function (t) { 6 | t.is(foo, 'foo'); 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/test/foo.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var test = require('ava'); 3 | var foo = require('../foo'); 4 | 5 | test(function (t) { 6 | t.is(foo, 'foo'); 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/test/has-syntax-errors.js: -------------------------------------------------------------------------------- 1 | 2 | this is not a java script file 3 | -------------------------------------------------------------------------------- /test/test-bundler.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import test from 'ava'; 4 | import resolveFrom from 'resolve-from'; 5 | import browserUnpack from 'browser-unpack'; 6 | import bundlerFactory from '../lib/test-bundler'; 7 | import {fixture} from './_utils'; 8 | 9 | // If we don't share a de-duped bluebird reference with, it affects the results of some bundles. 10 | // This isn't important for the plugin to work, just for some of the tests in this file. 11 | const avaDir = path.dirname(resolveFrom(__dirname, 'ava')); 12 | const avaBluebird = resolveFrom(avaDir, 'bluebird'); 13 | const thisBluebird = resolveFrom(__dirname, 'bluebird'); 14 | const sharedBluebird = avaBluebird === thisBluebird; 15 | 16 | if (!sharedBluebird) { 17 | console.warn('No shared bluebird. You aren\'t really testing the full de-dupe behavior of the browserify externals hack.'); 18 | } 19 | 20 | test('foo bundle does not contain any ava dependencies', async t => { 21 | const bundler = bundlerFactory(path.join(__dirname, '..'), false); 22 | const file = await bundler.bundleFile(fixture('test', 'foo')); 23 | const fileRows = browserUnpack(file); 24 | t.is(fileRows.length, sharedBluebird ? 2 : 3); 25 | }); 26 | 27 | test('new bundles can be created after ava bundle is complete', async t => { 28 | // This goal is a big part of why our bundler has so much code in it. 29 | // See: https://github.com/substack/node-browserify/pull/1577 30 | const bundler = bundlerFactory(path.join(__dirname, '..'), false); 31 | await bundler.ava; 32 | const file = await bundler.bundleFile(fixture('test', 'foo')); 33 | const fileRows = browserUnpack(file); 34 | t.is(fileRows.length, sharedBluebird ? 2 : 3); 35 | }); 36 | 37 | test('source can be provided to save reading from disk', async t => { 38 | // This goal is a big part of why our bundler has so much code in it. 39 | // See: https://github.com/substack/node-browserify/pull/1577 40 | const bundler = bundlerFactory(path.join(__dirname, '..'), false); 41 | await bundler.ava; 42 | const file = await bundler.bundleFile( 43 | fixture('test', 'has-syntax-errors.js'), 44 | fs.readFileSync(fixture('test', 'foo.js'), 'utf8') 45 | ); 46 | const fileRows = browserUnpack(file); 47 | t.is(fileRows.length, sharedBluebird ? 2 : 3); 48 | }); 49 | 50 | --------------------------------------------------------------------------------