I've got several projects that each use an identical Gruntfile to run tasks and put the output in their own dist folder. Folder setup:
MyProjects
- Project1
- src
- dist
- Project2
- src
- dist
.....
I can't figure out how to run Grunt at the top level (MyProjects) and still have the output generated in the correct dist folder dynamically.
Is there a way I can have Grunt put the output in the correct dist folder without having to hard code it into the Gruntfile? Something like:
dist: {
files: {
// destination : source js
'<% ProjectName %>/dist/app.js': '<% ProjectName %>/src/app.js'
},
Thanks
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
// Project configuration.
grunt.initConfig({
watch: {
scripts: {
files: ['src/**/*.js'],
tasks: ['browserify', 'file_append', 'concat'],
options: {
spawn: false
}
},
sass: {
files: "src/scss/*.scss",
tasks: ['sass', 'file_append', 'concat']
}
},
sass: {
dist: {
options: {
style: 'expanded'
},
files: {
// destination // source file
"format/css/styles.css": "src/scss/styles.scss"
}
},
options: {
sourcemap: "none",
style: "compact",
noCache: true
}
},
file_append: {
default_options: {
files: [
// Development build
{
append: "",
prepend: "",
input: "format/app.js",
output: "format/dev.app.js"
},
{
append: "</style>`)",
prepend: "document.body.insertAdjacentHTML('afterbegin', `\n<style>\n",
input: "format/css/styles.css",
output: "format/css/dev.styles.html"
},
// Production build
{
append: "</script>",
prepend: "<script>\n",
input: "format/app.js",
output: "format/prod.app.html"
},
{
append: "</style>",
prepend: "<style>\n",
input: "format/css/styles.css",
output: "format/css/prod.styles.html"
}
]
}
},
concat: {
options: {
seperator: '\n'
},
// Development build
dev: {
src: ['format/dev.app.js', 'format/css/dev.styles.html'],
dest: 'dev/dev.app.js'
},
// Production build
prod: {
src: ['format/prod.app.html', 'format/css/prod.styles.html'],
dest: 'dist/prod.app.html'
}
},
browserify: {
dist: {
files: {
// destination for transpiled js : source js
'format/app.js': 'src/app.js'
},
options: {
transform: [
[
'babelify', {
presets: "es2015",
comments: false,
plugins: "transform-object-rest-spread"
}
]
],
browserifyOptions: {
debug: false
}
}
}
}
});
grunt.registerTask('default', [
'sass',
'browserify:dist',
'file_append',
'concat',
'watch'
]);
};
There's a couple ways you can tackle this.
One option is to overload the arguments you pass to the task & include the folder name you wish to target.
grunt sass:dist:Project1
The additional argument is accessible via lodash templates which are a part of the GruntJS framework, and allows the configuration to be set at the time the task is ran:
sass: {
dist: {
options: {
style: 'expanded'
},
files: {
// destination // source file
"MyProjects/<%= grunt.task.current.args[0] %>/format/css/styles.css": "MyProjects/<%= grunt.task.current.args[0] %>/src/scss/styles.scss"
}
},
options: {
sourcemap: "none",
style: "compact",
noCache: true
}
}
This approach works in the context of the function that's executing, but it wouldn't continue to pass the args to the next task. To do that, we need to add a custom task which will set a configuration object before executing the task list:
grunt.registerTask("build", (project) => {
const buildConfig = { project };
grunt.config.set("build", buildConfig);
grunt.task.run([
'sass',
'browserify:dist',
'file_append',
'concat',
'watch'
]);
});
Now when we run grunt build:Project1, your custom task build will run and set the property we passed in the grunt config object. We can then reference that value in our other grunt config objects using lodash like we did for the first option. To access config values with lodash templates, we just have to provide the config pointer in json notation:
files: {
"MyProjects/<%= build.project %>/format/css/styles.css": "MyProjects/<%= build.project %>/src/scss/styles.scss"
}
Grunt compiles the configs required for a task at the time they're run & will process the lodash templates then, allowing you to inject your project name into a task. Since we stored the value in the config object, the value will persist through until grunt completes and exits.
My gruntfile thus far
uglify: {
src: [
...
],
dest: 'js/application.min.js',
options: {
'compress': {},
'reserveDOMCache': true,
'enclose': undefined,
'exportAll': false,
'expression': false,
'preserveComments': false,
'report': 'min',
'sourceMap': false,
'sourceMapIn': undefined,
'sourceMapIncludeSources': false,
'sourceMapName': undefined,
'wrap': undefined
},
development: {
options: {
'beautify': false,
'mangle': true
}
},
production: {
options: {
'beautify': true,
'mangle': false
}
}
}
However when I run the task uglify:development it will respond with No files created.
As far as I know this is not possible. You need to explicitly define a src for each target.
You could declare a variable outside of the config and add it to each target:
var mySources = ['file1.txt', 'file2.txt']; //declared outside config
development: {
src: mySources, //add variable to each target
Or you can declare a variable inside the config:
mySourcesInside: ['file1.txt'], //declared within config
development: {
src: '<%= mySourcesInside%>', //reference variable in each target
Alternatively you could use something like grunt-override-config https://github.com/masakura/grunt-override-config and declare just one uglify target and overrides for the options.
I'd like to make configuration of grunt tasks a bit easier. Currently I've got a lot of different configuration files, like a .csslintrc, jshintrc, bower.json and so on.
It would be really cool if I could concatenate all these configuration files into one single file. This configuration file could look something like
{
"csslint": {
"important": 1,
// ...
},
"jshint": {
//...
},
"bower": {
//...
}
}
My only solution so far would be using a preprocessor and simply insert the options in the tasks (I couldn't figure out how to insert options otherwise). But this doesn't seem to be a very beautiful way...
Most (all?) grunt tasks let you define options in the Gruntfile itself instead of their respective .*rc files.
So if you do that, you nearly get your one single configuration file for free.
For example:
jshint: {
dist: {
options: {
curly: true,
eqeqeq: true,
},
src: ['path/to/**/*.js']
}
},
csslint: {
dist: {
options: {
import: 2
},
src: ['path/to/**/*.css']
}
},
bower: {
install: {
options: {
targetDir: './lib',
layout: 'byType',
install: true,
verbose: false,
cleanTargetDir: false,
cleanBowerDir: false,
bowerOptions: {}
}
}
}
I was wondering if anyone has got grunt karma to run just one spec that is changed on watch. This is my config below. The problem is that the line grunt.config('karma.unit.options.files', filepath); doesn't seem to be doing anything as all the specs still get run however foo does get output before the karma:unit:run gets fired.
grunt.initConfig({
karma: {
unit: {
configFile: 'karma.conf.js',
background: true,
singleRun: false,
options: {
files: allFilesArray
}
}
},
watch: {
options: {
spawn: false,
livereload: true
},
karma: {
files: ['js/spec/**/*.spec.js', 'js/src/**/*.js'],
tasks: ['karma:unit:run']
}
}
})
grunt.event.on('watch', function (action, filepath){
console.log('foo');
grunt.config('karma.unit.options.files', filepath);
});
Is there anyone out there who has achieved running one spec in the terminal on file change? We have thousands of tests so it is starting to get slow.
Thanks,
Alex
I got this to work. Basically, you use watch with an event handler to dynamically change the karma config whenever a file changes. Here's the rundown:
My Grunt config has two karma tasks: "all" and "one". "all" runs all of them, and "one" only runs a single file which it does not know beforehand.
grunt.initConfig({
// ...
karma: {
all: {
configFile: 'karma.conf.js',
browsers: ['PhantomJS'],
singleRun: true,
options: {
files: [
'bower_components/jquery/dist/jquery.js', // dependencies
'src/js/**/*.js', // js source files
'src/js/**/*.spec.js' // unit test files
]
}
},
one: {
configFile: 'karma.conf.js',
browsers: ['PhantomJS'],
singleRun: true,
files: [
{src: 'bower_components/jquery/dist/jquery.js'}, // dependencies
{src: ['src/js/**/*.js','!src/js/**/*.spec.js']} // source files
// (exclude the unit test files)
// watchEventListener will add the unit test file to run
]
}
},
// ...
});
And then later in my gruntfile, I add a listener for watch events. This listener updates the karma:one task and adds the unit test file. We keep a copy of the original files array, or else our additions would persist and accumulate through the lifetime of the watch task.
// when a unit test changes, execute only it
var original_karmaOne_files = grunt.config.get('karma.one.files'); // keep the original files array
grunt.event.on('watch', function watchEventListener(action, filepath, target){
// this handler handles ALL watch changes. Try to filter out ones from other watch tasks
if (target == 'js_spec') handleJSHintSpec();
// ---------------------
function handleJSHintSpec() {
if (action == 'deleted') return; // we don't need to run any tests when a file is deleted
// this will probably fail if a watch task is triggered with multiple files at once
// dynamically change the config
grunt.config.set('karma.one.files', [].concat(original_karmaOne_files, [{src: filepath}]));
}
});
And here is my gruntfile's watch task:
watch: {
// ...
// when js spec files change,
// lint them
// run unit tests
js_spec: {
options: {
interrupt: true
},
files: 'src/js/**/*.spec.js',
tasks: ['jshint:js_spec', 'karma:one']
},
// ...
}
My karma.conf.js file is pretty default, but its files array is empty. Actually, I commented it out, so the property is undefined.
// list of files / patterns to load in the browser
//files: [], // specified in the gruntfile
TL; DR: Use karma:unit everywhere instead of karma:unit:run and use grunt.event.on('watch', function(){}); to edit the karma config to only include the test files you want to run.
I have this working, but it might not be what you want. I'm starting the server up again every time I save a file. Further explanation is below. Here is some of the config:
watch: {
tests: {
files: 'tests/**/*.js',
tasks: ['karma:unit']
},
tsChanged: {
files: config.tsFiles,
tasks: [
'ts',
'karma:unit'
]
}
}
grunt.event.on('watch', function(action, filepath){
grunt.config(['karma', 'unit', 'files'], [{
src: [
path/to/your/source/files,
path/to/your/test/file,
]
}]);
});
It seems to me that karma loads all of the app files and the test files into the browser whenever it starts the server. In your case, that would be when you enter "grunt karma:unit:start watch" into the command line. So here, I used "grunt watch" and just added a "karma:unit" to the process. Then I caught the save event and updated the karma config before it started up the server.
Hope this helps.
Using a combination of yargs and some runtime-magic, I do this:
var argv = require('yargs')
.default('t', '*.js')
.alias('t', 'tests')
.describe('t', 'A file or file pattern of the test files to run, relative to the test/unit dir')
.help('?')
.alias('?', 'help')
.argv;
var filesToLoad = ['src/**/*.js', 'test/unit/helpers/*.js'];
filesToLoad.push(path.join('test/unit/**', argv.t));
gulp.task('tdd', function (done) {
karma.start({
configFile: __dirname + '/../../karma.conf.js',
jspm: {
loadFiles: filesToLoad,
}
}, function(e) {
done();
});
});
Which takes a test file / path pattern as an argument to gulp and loads that in preference to all the files.
Based on the answer of Matthias and the comments my Grundfile.js is:
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
karma: {
all: {
configFile: 'karma.conf.js',
background: true,
files: [
{ src: './Leen.Managementsystem/bower_components/jquery/dist/jquery.js' },
{ src: './Leen.Managementsystem/bower_components/globalize/lib/globalize.js' },
{ src: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ src: './Leen.Managementsystem/App/**/*.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/test/*.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/**/*.spec.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/test-main.js' }
]
},
one: {
configFile: 'karma.conf.js',
files: [
{ src: './Leen.Managementsystem/bower_components/jquery/dist/jquery.js' },
{ src: './Leen.Managementsystem/bower_components/globalize/lib/globalize.js' },
{ src: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ src: './Leen.Managementsystem/App/**/*.js', included: false },
{ src: './Leen.Managementsystem.Tests/App/test/*.js', included: false },
// (do not inlcude the *.spec.js files here! The watch event listener will add the single spec file to run)
{ src: './Leen.Managementsystem.Tests/App/test-main.js' }
]
}
},
watch: {
spec_js: {
options: {
interrupt: true,
spawn: false
},
files: 'Leen.Managementsystem.Tests/App/**/*.spec.js',
tasks: ['karma:one:start']
}
}
});
var originalKarmaOneFiles = grunt.config.get('karma.one.files'); // keep the original files array
grunt.event.on('watch', function watchEventListener(action, filepath, target) {
if (target === 'spec_js') {
handleChangedSpecFile();
}
function handleChangedSpecFile() {
if (action === 'deleted') {
return;
}
var testFilePath = "./" + filepath.replace(/\\/g, "/");
grunt.log.writeln(['Running single karma test for: ' + testFilePath]);
var updatedFiles = originalKarmaOneFiles.concat([{ src: testFilePath, included: false }]);
grunt.config.set('karma.one.files', updatedFiles);
}
});
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['karma:all','watch']);
};
karma.conf.js:
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '', //the solution root path, e.g. D:\Energienetzwerke\trunk
// frameworks to use
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: [
'./Leen.Managementsystem/bower_components/jquery/dist/jquery.js',
'./Leen.Managementsystem/bower_components/globalize/lib/globalize.js',
{ pattern: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ pattern: './Leen.Managementsystem/App/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/*.js', included: false},
{ pattern: './Leen.Managementsystem.Tests/App/**/*.spec.js', included: false},
'./Leen.Managementsystem.Tests/App/test-main.js'
],
// list of files to exclude
exclude: [
'./Leen.Managementsystem/App/main.js'
],
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
reporters: ['progress', 'coverage', 'notify', 'htmlDetailed', 'xml'],
coverageReporter: {
dir: './Leen.Managementsystem.Tests/testCoverage',
reporters: [
{ type: 'html',subdir: 'html'},
{ type: 'cobertura',subdir: 'xml', file: 'coverage.xml' },
{ type: 'lcov', subdir: 'lcov' },
{ type: 'text-summary' }
]
},
notifyReporter: {
reportEachFailure: true, // Default: false, Will notify on every failed spec
reportSuccess: false // Default: true, Will notify when a suite was successful
},
htmlDetailed: {
autoReload: true,
dir: './Leen.Managementsystem.Tests/testResults'
},
preprocessors: {
// source files, that you wanna generate coverage for
// do not include tests or libraries
// (these files will be instrumented by Istanbul)
'./Leen.Managementsystem/App/**/*.js': ['coverage']
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false, //watching is done by Gruntfile.js to only execute changed tests
usePolling: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['Chrome_With_Saved_DevTools_Settings'],
customLaunchers: {
Chrome_With_Saved_DevTools_Settings: {
base: 'Chrome',
chromeDataDir: './.chrome'
}
},
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true
});
};
package.json:
{
"name": "solution",
"version": "1.0.0",
"description": "contains packages that are needed for running karma",
"main": "./Leen.Managementsystem.Tests/App/test-main.js",
"dependencies": {
"grunt": "1.0.1",
"grunt-cli": "1.2.0",
"grunt-contrib-watch": "1.0.0",
"grunt-karma": "2.0.0",
"jasmine-core": "2.6.4",
"karma": "1.7.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage": "1.1.1",
"karma-firefox-launcher": "1.0.1",
"karma-html-detailed-reporter": "1.1.20",
"karma-ie-launcher": "1.0.0",
"karma-jasmine": "1.1.0",
"karma-notify-reporter": "1.0.1",
"karma-phantomjs-launcher": "1.0.4",
"karma-requirejs": "1.1.0",
"karma-xml-reporter": "0.1.4",
"requirejs": "2.3.4"
},
"devDependencies": {},
"scripts": {
"test": "karma run"
},
"author": "LEEN",
"license": "private"
}
Does anyone know the best solution for automatically restarting node and running tests after the restart every time a file changes?
I am currently using grunt-contrib-watch with grunt-develop. I am getting an ECONNREFUSED error on the some restarts. I think it is because my tests are running before the server is fully online.
Any ideas on how best to achieve what I want?
What I want: Restart node and then run all integration tests after each file change.
I am taking a BDD approach to testing (as opposed to regular unit tests) with cucumber.js. I wanted to make sure that each test run against the API I was building started on a fresh boot-up of the application.
I figured it out. Here is what I used:
grunt-contrib-watch to monitor for file changes.
It in turn calls
grunt-develop to restart the application
grunt-cucumberjs to run the cucumber tests
I then modified my index.js (starts the app) so that it doesn't start the app if the NODE_ENV is set to test. That way the cucumber tests actually start the server and can wait till the start process has finished before running the tests.
Here is the GruntFile and Index file:
Gruntfile.js
/*jslint node: true */
"use strict";
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
env: {
dev: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'dev',
PORT: 8080
},
test: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'test',
PORT: 8081
},
coverage: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'test',
PORT: 8081
}
},
watch: {
js: {
files: [
'index.js',
'features/**/*.js',
'server/**/*.js'
],
tasks: ['develop', 'cucumberjs', 'jshint'],
options: {
nospawn: true
}
}
},
jshint: {
all: ['Gruntfile.js', 'index.js', 'server/**/*.js', 'features/**/*.js']
},
nodemon: {
dev: {
script: 'index.js'
}
},
cucumberjs: {
src: './features',
},
develop: {
server: {
file: 'index.js'
}
},
instrument: {
files: ['index.js', 'server/**/*.*'],
options: {
lazy: true,
basePath: 'coverage/instrument/'
}
},
storeCoverage: {
options: {
dir: 'coverage'
}
},
makeReport: {
src: 'coverage/coverage.json',
options: {
type: 'lcov',
dir: 'coverage/reports',
print: 'detail'
}
},
coverage: {
options: {
thresholds: {
'statements': 90,
'branches': 90,
'lines': 90,
'functions': 90
},
dir: 'coverage',
root: ''
}
}
});
grunt.loadNpmTasks('grunt-env');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-develop');
grunt.loadNpmTasks('grunt-cucumber');
grunt.loadNpmTasks('grunt-istanbul');
grunt.loadNpmTasks('grunt-istanbul-coverage');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['env:dev', 'nodemon']);
grunt.registerTask('test', ['env:test', 'watch']);
grunt.registerTask('testcoverage', ['env:test', 'jshint', 'instrument', 'cucumberjs', 'storeCoverage', 'makeReport', 'coverage']);
};
Index.js
/*jslint node: true */
"use strict";
var Hapi = require('hapi');
var Good = require('good');
var server = {};
exports.server = {
start: function(callback) {
/* istanbul ignore next */
var port = process.env.PORT || 8080;
server = new Hapi.Server(port);
var routes = require('./server/routes');
routes.register(server);
var exceptionHandling = require('./server/exceptionHandling');
exceptionHandling.register(server);
server.pack.register(Good, function(err) {
/* istanbul ignore if */
if (err) {
throw err; // something bad happened loading the plugin
}
/* istanbul ignore next */
server.log('info', 'Server starting at ' + server.info.uri);
server.start(callback);
});
},
stop: function(callback) {
server.log('info', 'Server stopping.');
server.stop(null, callback);
},
rootUrl: function() { return server.info.uri; }
};
/* istanbul ignore if */
if (process.env.NODE_ENV != 'test') {
exports.server.start(function() {});
}