gulp-karma suggests an external config file like this:
module.exports = {
basePath: '',
singleRun: true
};
I want an external config file like this (grunt-karma style):
module.exports = function(config) {
config.set({
basePath: '',
singleRun: true
});
};
How to use the proper config file with karma API.
Details:
I am using gulp-karma, and as mentioned
there,
i have to implement it on my own.
Karma API is very simple:
var server = require('karma').server;
server.start(config, done);
config variable is vague. It is a plain object with configuration:
var config = {
basePath: '',
singleRun: true
// ...
}
Let's take a look at grunt-karma:
Sample grunt-karma config:
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
grunt-karma configuration can take a configFile option, which is not documented anywhere.
I can see, i can pass a configFile option from karma source code:
var config = cfg.parseConfig(cliOptions.configFile, cliOptions);
Is there a documentation for the Karma API that mentions configFile option, how does grunt-karma know to
use it.
This question is surprisingly relevant, and most of these comments breeze over the actual question posed at the end of this post: "How does grunt-karma know to use a config object with a property called configFile.
I, too, have been wondering the same thing as that property is not listed anywhere in the karma config docs. For instance, if I would like to run my karma tests via the public API in my gulpfile. It would look like this:
...
var karmaConfig = require('spec/karma.conf.js');
// where my config file exports an object with properties like exclude:, path:
karma.start(karmaConfig)}, process.exit);
This would work fine, except if I wanted to export the config function so I can use the config constants (outlined here):
// allows me to do this
module.exports = function(config) {
config.set({
logLevel: config.LOG_INFO
});
}
But there's no way to get this to work unless you use something like this:
karma.start({configFile: 'spec/karma.conf.js'}, process.exit);
Where I simply pass in an object with a property configFile that points to the actual config file.
Karma docs don't mention this anywhere (as of this comment), but it's the only way to accomplish running my test via the API while using the config function export method.
Related
I am using Karma to run tests against some code.
Both the tests and the code are transpiled (ES6 => ES5 using babel) before being run by Karma.
This works great and the tests run fine.
But if I try and use the text! plugin from any of the files being tested...
import template from 'text!./template.html';
...I get:
There is no timestamp for /base/src/text.js!
Uncaught Error: Script error for "text", needed by: text!app/template.html_unnormalized2
http://requirejs.org/docs/errors.html#scripterror
Uncaught Error: Load timeout for modules: text!app/template.html_unnormalized2
Does anyone have any ideas why this might be?
The built artifact in the dist folder (i.e. the item under test) contains the successfully encoded text RequireJS items eg:
define('text!app/template.html',[],function () { return '<div>foo</div>';});
Additional Info
test-main.js
var TEST_REGEXP = /(spec|test)\.js$/i;
var allTestFiles = [];
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
allTestFiles.push(normalizedTestModule);
}
});
require.config({
baseUrl: '/base/src',
paths: {},
shim: {},
deps: allTestFiles,
callback: window.__karma__.start
});
karma.conf.js
module.exports = function(config) {
'use strict';
var path = require('path');
var cdn = 'http://localhost:55635/modules/';
var basePath = path.dirname(__filename);
config.set({
basePath: '../../..',
frameworks: [
'requirejs',
'jasmine'
],
files: [
{
pattern: path.join(basePath, 'test-transpiled', '*-spec.js'),
included: false
},
path.join(basePath, 'dist', 'artifacts', 'app.js'),
path.join(basePath, 'test', 'unit', 'test-main.js')
],
proxies: {
'/cdn/': cdn
},
exclude: [],
preprocessors: {},
reporters: ['dots'],
colors: true,
autoWatch: false,
singleRun: false,
browsers: ['Chrome'],
});
};
Edit:
I have added the following to karma.conf.js:
files: [
{
pattern: path.join(basePath, 'node_modules/require-plugins/text/text.js'),
included: false
},
// ...
],
I continue to get an error when running the tests:
There is no timestamp for /base/src/text.js!
Presumably because I need to add "text" to the paths section of test-main.js?
require.config({
baseUrl: '/base/src',
paths: {
'text': '../node_modules/require-plugins/text/text'
},
// ...
});
But I have tried various combinations of baseUrl and the path in the text path and I cannot get it to stop 404-ing.
Your files option in karma.conf.js does not include the text plugin, which is why you get the error that there's no timestamp for it.
Add an item to your files list that hits the text plugin on your file system, and make sure that you have included: false for it. RequireJS plugins are like other modules: RequireJS must be able to load them to use them.
You may need to also set paths in test-main.js depending on where you put your plugin. RequireJS already is looking for it at /base/src/text.js. If you locate it so that the plugin is served at this URL, then there's no need to set paths. If you put it somewhere else, then you do need to set paths. Something like:
paths: {
text: 'path/to/text',
}
Remember that the paths in paths are interpreted relative to your baseUrl setting.
I tried using the require.js text! plugin, and also found that it conflicts with the baseUrl defined for the rest of the project.
The requirejs.config sets the baseUrl to the parent directory of the JS files, while my templates are defined in a sibling directory to the js.
There was no way to tell requirejs to load templates from /base/templates and js from base/js.
My solution was to change the text.js plugin, and add a hook to change the url just before the ajax call is made to fetch the HTML file. You can take my version of text.js from here.
For example we have multiple entries:
entry: {
main: "./app/entry.js",
view: "./app/entry.js",
},
how to pass current name (main or view) into entry.js?
Ideal solution will be something like this:
new webpack.DefinePlugin({
'_ENTRY_': '[name]'
}),
like other config options can have, but of course DefinePlugin dont know how to process this...
If you're running the code inside Node.js then you can use __filename
and __dirname.
Webpack can mock them for non-Node.js environments.
Please see Webpack Node configuration
node: {
__filename: true,
__dirname: true
}
In this case you can use __filename and __dirname globals as usually done in Node.js enviroments.
if(__filename.indexOf('index.js') != -1) {
// Code here
}
node.__filename
Default: "mock"
Options:
true: The filename of the input file relative to the context option.
false: The regular Node.js __filename behavior. The filename of the output file when run in a Node.js environment.
"mock": The fixed value "index.js".
So, I've been looking all over for this, found "similar" answers here, but not exactly what I want.
Right now if I want to test a single file with karma, I need to do fit(), fdescribe() on the file in question...
However, what I do want is to be able to just call karma, with the config file, and direct it to a specific file, so I don't need to modify the file at all, ie:
karma run --conf karma.conf.js --file /path/to/specific/test_file.js
is it possible to do this? Or with any helper? (using grunt or gulp?)
First you need to start karma server with
karma start
Then, you can use grep to filter a specific test or describe block:
karma run -- --grep=testDescriptionFilter
Even though --files is no longer supported, you can use an env variable to provide a list of files:
// karma.conf.js
function getSpecs(specList) {
if (specList) {
return specList.split(',')
} else {
return ['**/*_spec.js'] // whatever your default glob is
}
}
module.exports = function(config) {
config.set({
//...
files: ['app.js'].concat(getSpecs(process.env.KARMA_SPECS))
});
});
Then in CLI:
$ env KARMA_SPECS="spec1.js,spec2.js" karma start karma.conf.js --single-run
This option is no longer supported in recent versions of karma:
see https://github.com/karma-runner/karma/issues/1731#issuecomment-174227054
The files array can be redefined using the CLI as such:
karma start --files=Array("test/Spec/services/myServiceSpec.js")
or escaped:
karma start --files=Array\(\"test/Spec/services/myServiceSpec.js\"\)
References
karma-runner source: cli.js
karma-runner source: config.js
I tried #Yuriy Kharchenko's solution but ran into a Expected string or object with "pattern" property error.
Therefore I made the following modifications to his answer and now I'm able to run single files using Karma:
function getSpecs(specList) {
if (specList) {
return specList.toString();
} else {
return ['**/*_spec.js'] // whatever your default glob is
}
}
module.exports = function(config) {
config.set({
//...
files: [
{ pattern: getSpecs(process.env.KARMA_SPECS), type: "module"}
]
});
});
Note: This solution only works with a single file mentioned in the KARMA_SPECS env variable. Ex: export KARMA_SPECS="src/plugins/muc-views/tests/spec1.js"
I'm using Karma with Jasmine for my tests. In some tests, I have large objects that the test relies on. When I do something like
expect(obj).toEqual(expectedObj);
and obj != expectedObj, I get an error message in my terminal. But this error is really long, because it includes both of the objects, and it's very hard to see, in what parts the two objects are different.
So, is there any highlighter for the terminal, that can be used along with karma? This way, it would be much more easy to figure out, what's wrong.
I had the same problem and what did it for me was karma-jasmine-diff-reporter.
Just install it:
npm install karma-jasmine-diff-reporter --save-dev
and configure it as a reporter, eg:
// karma.conf.js
module.exports = function(config) {
config.set({
reporters: ['jasmine-diff']
});
};
You can configure it to pretty print:
// karma.conf.js
module.exports = function(config) {
config.set({
reporters: ['jasmine-diff'],
jasmineDiffReporter: {
pretty: true, // 2 spaces by default for one indent level
matchers: {
toEqual: {
pretty: false // disable pretty print for toEqual
}
}
}
});
};
Output will be something like this:
Project Intro
My project is a single page storefront. The project has multiple modules, and each module contains a set of controller.js, view.js and model.js files, as well as a template.html file. And uses requirejs to manage dependencies.
Problem Statement
I want to use mainConfigFile to provide paths to reference modules in grunt-requirejs.
Part of my mainConfigFile's require.config is stored in separate file (base.dependency.config), and require.config.paths are pieced together by underscore at runtime.
base.dependency.config
config = {
baseDependencyConfig: {
paths: { ... }
shim: { ... }
}
}
main.js
var dependencies = config.baseDependencyConfig;
var basePaths = config.baseDependencyConfig.paths;
var extensionPaths = {
// extra sets of paths
};
// combine base paths and extension paths at runtime using underscore
var dependencyPaths = _.extend(basePaths, extensionPaths);
dependencies.paths = dependencyPaths;
require.config(dependencies);
// application startup
require(['app', 'eventbus']) {
// code
}
Error
However, grunt requirejs is ignoring mainConfigFile, grunt requirejs tries to find 'app.js' under root, when in fact, 'app' is defined under require.config paths as
'app': 'modules/base/app/base.app.controller'
my gruntFile:
module.exports = function (grunt) {
grunt.initConfig({
// ... other plugin config
requirejs: {
options: {
baseUrl: 'public',
// the paths for the named modules such as 'app' are defined
// in main.js under require.config paths
name: 'main',
include: [
'app',
'cart',
'category'
],
out: 'public/build/app-optimized.js',
mainConfigFile: 'public/main.js',
findNestedDependencies: true,
optimizeCss: 'none',
cssImportIgnore: 'style/style.css, style/mocha.css',
}
}
})
}
my file structure
public
|--modules/
| |--base/
| | |--cart
| | |--category
| | |--category.controller.js
| | |--category.view.js
| | |--category.model.js
| | └-category.template.html
| |
| └--extension/
|
|--style/
|--image/
|--main.js <-- main config file
|--other .js files
mainConfigFile, main.js lives in root, along with a few other application startup js files
main bulk of application files lives inside modules folder
each module folder contains its controller, view and model js file, as well as a template.html file
Edit
the gruntFile worked before, with different mainConfigFile (main.js) setup:
require.config({
paths: {...}
shim: {...}
})
// application startup
require(['app', 'eventbus']) {
// code
}
r.js uses Esprima as Javascript parser to extract the config object from the specified mainConfigFile. It only looks for certain signatures in the code.
Look at
hasRequire(): determine the AST node is a configuration call candidate
findConfig(): calls the above deciding how to extract the config
I've created a patch making it aware of recognizing
requirejs.config(_VariableToExport = { ... });
This is limited and the Javascript parser approach makes it very complicated to make r.js able to extract configurations that were created by functions (like in your code) or so. This patch has not been submitted to the project yet. I'm struggling with bureaucracy yet.
I think the only working workaround so far is
not to use mainConfigFile
exporting the config as NodeJS module
requiring the main.js/config.js in Node (Grunt)
passing the object as value to the config attribute or method
See my comment to this issue for a scribble.
This approach is proven in another, a bit older project I'm working on.
Related to r.js 2.1.11.