diff --git a/README.md b/README.md
index f6bf9c2..0c99f9f 100644
--- a/README.md
+++ b/README.md
@@ -141,6 +141,8 @@ It uses only one target: `html`, with a list of the concerned files. For example
By default, it will consider the directory where the looked-at file is located as the 'root' filesystem. Each relative path (for example to a javascript file) will be resolved from this path. Same goes for the absolute ones.
If you need to change the 'root' dir, use the `root` option (see below).
+Note that `useminPrepare` and `usemin` tasks will both throw an error if a resource is not found.
+
```js
useminPrepare: {
html: 'index.html'
@@ -255,6 +257,84 @@ useminPrepare: {
The given steps or post-processors may be specified as strings (for the default steps and post-processors), or as an object (for the user-defined ones).
+### resolveSource
+
+Type: 'function'
+Default: `undefined`
+
+This is an optional hook allowing applications to override how source urls
+are resolved to filesystem paths. This function is called with the signature
+`resolveSource(sourceUrl, fileDir, fileName, blockTarget, searchPath)`, where:
+
+ * `sourceUrl` is the source reference being resolved,
+ * `fileDir` and `fileName` are respectively, the directory and name of the file, the reference was found in,
+ * `blockTarget` is the target path for the block the reference occurred within,
+ * `searchPath` is an array of the directories that would be searched by default.
+
+The return value of this function should be :
+ * the path to the source file,
+ * `null` to indicate the default resolution rules should be used to resolve `sourceUrl`,
+ * `false` to indicate the file cannot be resolved, thus default resolution rules are not used.
+
+The default resolution is to search the `sourceUrl` in all provided entries in `searchPath` array.
+
+For example:
+
+* To override a specific URL prefix, and use the default behavior for all others:
+
+```js
+'useminPrepare', {
+ options: {
+ resolveSource: function (sourceUrl, fileDir, fileName, blockTarget, searchPath) {
+ var fs = require('fs');
+ var path = require('path');
+
+ var match = sourceUrl.match(/^\/alt-static-url\/(.*)/);
+ if (match) {
+ var source = path.join("alt-static-location", match[1]);
+ // return the override source path if found on filesystem
+ // else will default to null which means that resource will be search using
+ // default behavior
+ if (fs.existsSync(source)) {
+ return source;
+ }
+ /*
+ // You might be interested in return false if source cannot be resolved using the
+ // following code instead:
+ // it will return the overriden source path if resource found on filesystem
+ // or false if not found, meaning default behavior will not be used
+ return fs.existsSync(source) ? source : false;
+ */
+ }
+ // returning null will match the default behavior for any url not matching
+ // the prefix `^/alt-static-url/`
+ return null;
+ }
+ }
+}
+```
+
+* To replicate the default behavior, use the following version. It should be noted that this is only for illustration purpose, as the default behavior will be used if no `resolveSource` hook is provided, or if `resolveSource` fails to resolve the sourceUrl, hence returning null.
+
+```js
+'useminPrepare', {
+ options: {
+ resolveSource: function (sourceUrl, fileDir, fileName, blockTarget, searchPath) {
+ var fs = require('fs');
+ var path = require('path');
+ for (var i = 0; i < searchPath.length; ++i) {
+ var source = path.join(searchPath[i], fname);
+ if (fs.existsSync(source)) {
+ return source;
+ }
+ }
+ // source not found, so returning false as it cannot be resolved
+ return false;
+ }
+ }
+}
+```
+
#### User-defined steps and post-processors
User-defined steps and post-processors must have 2 attributes:
diff --git a/lib/config/concat.js b/lib/config/concat.js
index 0fbe54a..d9af7f6 100644
--- a/lib/config/concat.js
+++ b/lib/config/concat.js
@@ -1,7 +1,5 @@
'use strict';
var path = require('path');
-var fs = require('fs');
-var _ = require('lodash');
exports.name = 'concat';
@@ -21,23 +19,7 @@ exports.createConfig = function (context, block) {
// Depending whether or not we're the last of the step we're not going to output the same thing
var files = {};
files.dest = outfile;
- files.src = [];
-
- context.inFiles.forEach(function (f) {
- if (_.isArray(context.inDir)) {
- context.inDir.every(function (d) {
- var joinedPath = path.join(d, f);
- var joinedPathExists = fs.existsSync(joinedPath);
- if (joinedPathExists) {
- files.src.push(joinedPath);
- }
- return !joinedPathExists;
- });
- } else {
- files.src.push(path.join(context.inDir, f));
- }
- });
-
+ files.src = context.inFiles.map(context.resolveInFile, context);
cfg.files.push(files);
context.outFiles = [block.dest];
return cfg;
diff --git a/lib/config/cssmin.js b/lib/config/cssmin.js
index 82bda44..6f95e51 100644
--- a/lib/config/cssmin.js
+++ b/lib/config/cssmin.js
@@ -19,10 +19,7 @@ exports.createConfig = function (context, block) {
// Depending whether or not we're the last of the step we're not going to output the same thing
var files = {};
files.dest = outfile;
- files.src = [];
- context.inFiles.forEach(function (f) {
- files.src.push(path.join(context.inDir, f));
- });
+ files.src = context.inFiles.map(context.resolveInFile, context);
cfg.files.push(files);
context.outFiles = [block.dest];
return cfg;
diff --git a/lib/config/uglify.js b/lib/config/uglify.js
index 570ab5a..fe4cf71 100644
--- a/lib/config/uglify.js
+++ b/lib/config/uglify.js
@@ -23,15 +23,13 @@ exports.createConfig = function (context, block) {
var files = {};
var ofile = path.join(context.outDir, block.dest);
files.dest = ofile;
- files.src = context.inFiles.map(function (fname) {
- return path.join(context.inDir, fname);
- });
- // cfg[ofile] = context.inFiles.map(function (fname) { return path.join(context.inDir, fname);});
+ files.src = context.inFiles.map(context.resolveInFile, context);
+ // cfg[ofile] = context.inFiles.map(context.resolveInFile, context);
cfg.files.push(files);
context.outFiles.push(block.dest);
} else {
context.inFiles.forEach(function (fname) {
- var file = path.join(context.inDir, fname);
+ var file = context.resolveInFile(fname);
var outfile = path.join(context.outDir, fname);
cfg.files.push({
src: [file],
diff --git a/lib/configwriter.js b/lib/configwriter.js
index a74ea29..133ae0c 100644
--- a/lib/configwriter.js
+++ b/lib/configwriter.js
@@ -1,15 +1,17 @@
'use strict';
var path = require('path');
+var fs = require('fs');
+var grunt = require('grunt');
var File = require('./file');
var _ = require('lodash');
var deepMerge = function (origCfg, cfg) {
var outCfg = origCfg;
-// If the newly generated part is already in the config file
-// with the same destination update only the source part, instead of add the whole block again.
-// This way the same targets wont be regenerated multiple times if usemin is called
-// multiple times which can happen with grunt-watcher spawn:false mode (#307)
+ // If the newly generated part is already in the config file
+ // with the same destination update only the source part, instead of add the whole block again.
+ // This way the same targets wont be regenerated multiple times if usemin is called
+ // multiple times which can happen with grunt-watcher spawn:false mode (#307)
if (origCfg.files && cfg.files) {
var done = false;
@@ -54,13 +56,17 @@ var deepMerge = function (origCfg, cfg) {
// - Deliver for each block the requested file under dist directory
//
//
-var ConfigWriter = module.exports = function (flow, dirs) {
+var ConfigWriter = module.exports = function (flow, dirs, options) {
var self = this;
this.flow = flow;
// FIXME: check dest and staging are furnished
this.root = dirs.root;
this.dest = dirs.dest;
this.staging = dirs.staging;
+
+ // optional hook to resolve source url
+ this.resolveSource = options && options.resolveSource;
+
this.steps = {};
this.postprocessors = [];
this.destinations = {};
@@ -152,6 +158,8 @@ ConfigWriter.prototype.forEachStep = function (blockType, cb) {
ConfigWriter.prototype.process = function (file, config) {
var self = this;
var lfile = file;
+ var rootPath = self.root || [];
+ if (!Array.isArray(rootPath)) { rootPath = [rootPath]; }
config = config || {};
@@ -160,16 +168,60 @@ ConfigWriter.prototype.process = function (file, config) {
}
lfile.blocks.forEach(function (block) {
- // FIXME: support several searchPath...
+ // NOTE: initial context.inDir is only used by ./config/requirejs,
+ // everything else uses context.resolveInFile(),
+ // which checks entire searchPath.
+ var searchPath = block.searchPath.concat(rootPath, lfile.searchPath);
var context = {
inDir: self.root || lfile.searchPath[0],
inFiles: block.src,
outFiles: []
};
- if (block.searchPath.length > 0) {
- // FIXME: we must use all the furnished directories
- context.inDir = block.searchPath[0];
+ function resolveInFile (fname) {
+ var source = null;
+ // if a resolveSource hook function is provided use it for file source
+ if (self.resolveSource) {
+ source = self.resolveSource(fname, lfile.dir, lfile.name, block.dest, searchPath);
+ if (source) {
+ if (!fs.existsSync(source)) {
+ grunt.fail.warn(_.template(
+ 'usemin: resolveSource() returned non-existent path "<%= source %>"' +
+ ' for source "<%= fname %>".')(
+ {
+ source: source,
+ fname: fname
+ }));
+ }
+ return source;
+ }
+ }
+ // default behavior to search file in provided search paths.
+ // * source being null meaning that no resolveSource hook provided
+ // * source being false meaning that
+ if (source !== false) {
+ for (var i = 0; i < searchPath.length; ++i) {
+ source = path.join(searchPath[i], fname);
+ if (fs.existsSync(source)) {
+ return source;
+ }
+ }
+ }
+ grunt.fail.warn(_.template(
+ 'usemin: can\'t resolve source reference "<%= fname %>" ' +
+ '(file "<%= filepath %>", block "<%= block.dest %>").')(
+ {
+ fname: fname,
+ block: block,
+ filepath: path.join(lfile.dir, lfile.name)
+ }));
+ // we know it's not there, but return placeholder to match old behavior.
+ return path.join(searchPath[0], fname);
+ }
+ context.resolveInFile = resolveInFile;
+
+ function resolveTempFile(fname) {
+ return path.join(context.inDir, fname);
}
self.forEachStep(block.type, function (writer, last) {
@@ -207,15 +259,14 @@ ConfigWriter.prototype.process = function (file, config) {
}
context.inDir = context.outDir;
context.inFiles = context.outFiles;
+ context.resolveInFile = resolveTempFile;
context.outFiles = [];
context.options = null;
});
- context.inDir = lfile.searchPath[0];
- if (block.searchPath.length > 0) {
- context.inDir = block.searchPath[0];
- }
+ context.inDir = searchPath[0];
context.inFiles = block.src;
+ context.resolveInFile = resolveInFile;
if (self.postprocessors.hasOwnProperty(block.type)) {
self.postprocessors[block.type].forEach(function (pp) {
diff --git a/tasks/usemin.js b/tasks/usemin.js
index 9a4c6ca..21ad81b 100644
--- a/tasks/usemin.js
+++ b/tasks/usemin.js
@@ -111,6 +111,7 @@ module.exports = function (grunt) {
var blockReplacements = options.blockReplacements || {};
debug('Looking at %s target', this.target);
+
var patterns = [];
var type = this.target;
@@ -132,6 +133,11 @@ module.exports = function (grunt) {
nonull: true,
filter: 'isFile'
}, fileObj.src);
+
+ if (!files.length) {
+ grunt.fail.warn('No files found for target ' + type);
+ }
+
files.forEach(function (filename) {
debug('looking at file %s', filename);
@@ -158,6 +164,10 @@ module.exports = function (grunt) {
var staging = options.staging || '.tmp';
var root = options.root;
+ if (!this.filesSrc.length) {
+ grunt.fail.warn('No source file found.');
+ }
+
grunt.verbose
.writeln('Going through ' + grunt.log.wordlist(this.filesSrc) + ' to update the config')
.writeln('Looking for build script HTML comment blocks');
@@ -168,7 +178,7 @@ module.exports = function (grunt) {
root: root,
dest: dest,
staging: staging
- });
+ }, options);
var cfgNames = [];
c.stepWriters().forEach(function (i) {
diff --git a/test/fixtures/usemin.html b/test/fixtures/usemin.html
index a795f77..2d59441 100644
--- a/test/fixtures/usemin.html
+++ b/test/fixtures/usemin.html
@@ -57,18 +57,6 @@
Enjoy coding! - Yeoman
-
-
-
-
-
-
-
-
-
-
-
-
New
diff --git a/test/helpers.js b/test/helpers.js
index 0194dee..0ca7666 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -5,6 +5,7 @@ var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var _ = require('lodash');
var helpers = module.exports;
+var grunt = require('grunt');
helpers.directory = function directory(dir) {
return function directory(done) {
@@ -139,3 +140,18 @@ helpers.normalize = function (object) {
return object;
};
+
+var warn = grunt.fail.warn;
+
+// mock grunt.fail.warn to avoid breaking test
+helpers.mockGruntFailWarn = function (ctx, name) {
+ name = name || 'warnMessage';
+ grunt.fail.warn = function (message) {
+ ctx[name] = message;
+ };
+};
+
+// mock grunt.fail.warn to avoid breaking test
+helpers.restoreGruntFailWarn = function () {
+ grunt.fail.warn = warn;
+};
diff --git a/test/test-config-concat.js b/test/test-config-concat.js
index 2ec598d..c075ad6 100644
--- a/test/test-config-concat.js
+++ b/test/test-config-concat.js
@@ -25,14 +25,22 @@ describe('Concat config write', function () {
assert.equal(concatConfig.name, 'concat');
});
+ function resolveInFile(fname) {
+ // jshint -W040
+ return path.join(this.inDir, fname);
+ // jshint +W040
+ }
+
it('should use the input files correctly', function () {
var ctx = {
inDir: '.',
inFiles: ['foo.js', 'bar.js', 'baz.js'],
outDir: 'tmp/concat',
- outFiles: []
+ outFiles: [],
+ resolveInFile: resolveInFile
};
var cfg = concatConfig.createConfig(ctx, block);
+
assert.ok(cfg.files);
assert.equal(cfg.files.length, 1);
var files = cfg.files[0];
diff --git a/test/test-config-uglifyjs.js b/test/test-config-uglifyjs.js
index 1e11670..6bf1b46 100644
--- a/test/test-config-uglifyjs.js
+++ b/test/test-config-uglifyjs.js
@@ -25,18 +25,35 @@ describe('Uglify config write', function () {
assert.equal(uglifyConfig.name, 'uglify');
});
+ function resolveInFile(fname) {
+ // jshint -W040
+ return path.join(this.inDir, fname);
+ // jshint +W040
+ }
+
it('should use the input files correctly', function () {
var ctx = {
inDir: 'zzz',
inFiles: ['foo.js', 'bar.js', 'baz.js'],
outDir: 'tmp/uglify',
- outFiles: []
+ outFiles: [],
+ resolveInFile: resolveInFile
};
var cfg = uglifyConfig.createConfig(ctx, block);
+
assert.ok(cfg.files);
assert.equal(cfg.files.length, 3);
- var dests = ['tmp/uglify/foo.js', 'tmp/uglify/bar.js', 'tmp/uglify/baz.js'];
- var srcs = ['zzz/foo.js', 'zzz/bar.js', 'zzz/baz.js'];
+
+ var dests = [
+ 'tmp/uglify/foo.js',
+ 'tmp/uglify/bar.js',
+ 'tmp/uglify/baz.js'
+ ];
+ var srcs = [
+ 'zzz/foo.js',
+ 'zzz/bar.js',
+ 'zzz/baz.js'
+ ];
cfg.files.forEach(function (files, idx) {
assert.ok(files.src);
@@ -54,14 +71,22 @@ describe('Uglify config write', function () {
inFiles: ['foo.js', 'bar.js', 'baz.js'],
outDir: 'dist',
outFiles: [],
- last: true
+ last: true,
+ resolveInFile: resolveInFile
};
var cfg = uglifyConfig.createConfig(ctx, block);
+
assert.ok(cfg.files);
assert.equal(cfg.files.length, 1);
+
var files = cfg.files[0];
+
assert.equal(files.dest, path.normalize('dist/scripts/site.js'));
- assert.deepEqual(files.src, [path.normalize('zzz/foo.js'), path.normalize('zzz/bar.js'), path.normalize('zzz/baz.js')]);
+ assert.deepEqual(files.src, [
+ path.normalize('zzz/foo.js'),
+ path.normalize('zzz/bar.js'),
+ path.normalize('zzz/baz.js')
+ ]);
assert.deepEqual(ctx.outFiles, ['scripts/site.js']);
});
diff --git a/test/test-config-writer.js b/test/test-config-writer.js
index 56f8587..9d62cb3 100644
--- a/test/test-config-writer.js
+++ b/test/test-config-writer.js
@@ -1,4 +1,5 @@
'use strict';
+var path = require('path');
var assert = require('assert');
var helpers = require('./helpers');
var Flow = require('../lib/flow.js');
@@ -40,6 +41,15 @@ describe('ConfigWriter', function () {
});
describe('process', function () {
+ beforeEach(helpers.directory('temp'));
+ beforeEach(function () {
+ helpers.file.mkdir('app');
+ helpers.file.write(path.join('app', 'foo.js'), 'var foo=1;');
+ helpers.file.write(path.join('app', 'bar.js'), 'var bar=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var baz=1;');
+ helpers.file.write(path.join('app', 'fail.js'), 'var fail=1;');
+ });
+
var blocks = helpers.blocks();
it('should check for input parameters');
@@ -56,6 +66,7 @@ describe('ConfigWriter', function () {
dest: 'dist',
staging: '.tmp'
});
+
var config = c.process(file);
var expected = helpers.normalize({
concat: {
@@ -79,6 +90,66 @@ describe('ConfigWriter', function () {
assert.deepEqual(config, expected);
});
+ it('should detect missing sources', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+
+ var blocks = helpers.blocks();
+ blocks[0].src = ['foo.js'];
+
+ var file = helpers.createFile('foo', 'warn-missing', blocks);
+ var c = new ConfigWriter(flow, {
+ input: 'warn-missing',
+ dest: 'dist',
+ staging: '.tmp'
+ });
+
+ // mock grunt.fail.warn
+ helpers.mockGruntFailWarn(this);
+
+ c.process(file);
+ assert.ok(/can't resolve source reference "foo.js"/.test(this.warnMessage));
+
+ this.warnMessage = ''; // clear log output
+
+ // mock file
+ helpers.file.mkdir('warn-missing');
+ helpers.file.write(path.join('warn-missing', 'foo.js'), 'var a=1;');
+
+ // process file
+ c.process(file);
+
+ // warning log should now be silent
+ assert.ok(this.warnMessage === '');
+
+ // restore grunt.fail.warn
+ helpers.restoreGruntFailWarn();
+ });
+
+ it('should search all root paths', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+
+ var blocks = helpers.blocks();
+ blocks[0].src = ['foo.js', 'bar.js'];
+
+ var file = helpers.createFile('foo', 'app', blocks);
+
+ var c = new ConfigWriter(flow, {
+ root: ['dir1', 'dir2'],
+ dest: 'dist',
+ staging: '.tmp'
+ });
+
+ c.process(file);
+ });
+
it('should have a configurable destination directory', function () {
var flow = new Flow({
steps: {
@@ -262,6 +333,7 @@ describe('ConfigWriter', function () {
});
assert.deepEqual(config, expected);
});
+
it('should allow for a flow per block type');
it('should allow for an empty flow');
it('should allow for a filename as input');
@@ -301,6 +373,7 @@ describe('ConfigWriter', function () {
assert.deepEqual(config, expected);
});
+
it('should deduplicate blocks across files', function () {
var flow = new Flow({
steps: {
@@ -345,6 +418,7 @@ describe('ConfigWriter', function () {
assert.deepEqual(firstConfig, expectedFirst);
assert.deepEqual(repeatConfig, expectedRepeat);
});
+
it('should throw with conflicting blocks', function () {
var flow = new Flow({
steps: {
@@ -377,49 +451,224 @@ describe('ConfigWriter', function () {
dest: 'dist',
staging: '.tmp'
});
+
assert.throws(function () {
c.process(file);
});
});
- describe('stepWriters', function () {
- it('should return all writers if called without block type', function () {
- var flow = new Flow({
- steps: {
- js: ['concat', 'uglify'],
- css: ['concat']
- }
- });
- var c = new ConfigWriter(flow, {
- input: 'app',
- dest: 'destination',
- staging: '.tmp'
- });
- var names = [];
- c.stepWriters().forEach(function (i) {
- names.push(i.name);
- });
- assert.deepEqual(names, ['concat', 'uglify']);
- });
- });
-
it('should not add the same block multiple times though we call process() explicitly 2 times.', function () {
- var flow = new Flow({steps: {js: ['concat', 'uglify']}});
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
var file = helpers.createFile('foo', 'app', blocks);
- var c = new ConfigWriter(flow, {input: 'app', dest: 'destination', staging: '.tmp'});
+ var c = new ConfigWriter(flow, {
+ input: 'app',
+ dest: 'destination',
+ staging: '.tmp'
+ });
+
var config = c.process(file);
config = c.process(file, config); // This second process is intentional. Details are in issue #307.
+
var expected = helpers.normalize({
- concat: {generated: {files: [
- {dest: '.tmp/concat/scripts/site.js', src: ['app/foo.js', 'app/bar.js', 'app/baz.js']}
- ]}},
- uglify: {generated: {files: [
- {dest: 'destination/scripts/site.js', src: ['.tmp/concat/scripts/site.js']}
- ]}}
+ concat: {
+ generated: {
+ files: [{
+ dest: '.tmp/concat/scripts/site.js',
+ src: ['app/foo.js', 'app/bar.js', 'app/baz.js']
+ }]
+ }
+ },
+ uglify: {
+ generated: {
+ files: [{
+ dest: 'destination/scripts/site.js',
+ src: ['.tmp/concat/scripts/site.js']
+ }]
+ }
+ }
});
assert.deepEqual(config, expected);
});
});
+
+ describe('stepWriters', function () {
+ it('should return all writers if called without block type', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify'],
+ css: ['concat']
+ }
+ });
+ var c = new ConfigWriter(flow, {
+ input: 'app',
+ dest: 'destination',
+ staging: '.tmp'
+ });
+
+ var names = [];
+ c.stepWriters().forEach(function (i) {
+ names.push(i.name);
+ });
+
+ assert.deepEqual(names, ['concat', 'uglify']);
+ });
+ });
+
+ describe('resolveSource hook option', function () {
+ beforeEach(helpers.directory('temp'));
+
+ it('should be invoked for each block', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+ var blocks = helpers.blocks();
+ var file = helpers.createFile('foo', 'app', blocks);
+ var queue = [];
+ function resolveSource() {
+ queue.push(Array.prototype.slice.call(arguments));
+ return null;
+ }
+ var c = new ConfigWriter(flow, {
+ root: 'app',
+ dest: 'dist',
+ staging: '.tmp'
+ }, {
+ resolveSource: resolveSource
+ });
+
+ helpers.file.mkdir('app');
+ helpers.file.write(path.join('app', 'foo.js'), 'var foo=1;');
+ helpers.file.write(path.join('app', 'bar.js'), 'var bar=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var baz=1;');
+
+ c.process(file);
+ assert.deepEqual(queue, [
+ ['foo.js', 'app', 'foo', 'scripts/site.js', ['app', 'app']],
+ ['bar.js', 'app', 'foo', 'scripts/site.js', ['app', 'app']],
+ ['baz.js', 'app', 'foo', 'scripts/site.js', ['app', 'app']],
+ ]);
+ });
+
+ it('should override normal search when it returns a string', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+
+ var blocks = helpers.blocks();
+ var file = helpers.createFile('foo', 'app', blocks);
+
+ function resolveSource(sourceUrl) {
+ if (sourceUrl === 'foo.js') {
+ return path.join('dir2', 'foo2.js');
+ }
+ return null;
+ }
+ var c = new ConfigWriter(flow, {
+ root: 'app',
+ dest: 'dist',
+ staging: '.tmp'
+ }, {
+ resolveSource: resolveSource
+ });
+
+ helpers.file.mkdir('app');
+ helpers.file.mkdir('dir2');
+ helpers.file.write(path.join('app', 'bar.js'), 'var a=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var a=1;');
+ helpers.file.write(path.join('dir2', 'foo2.js'), 'var a=1;');
+
+ c.process(file);
+ });
+
+ it('should be prevented from returning non-existent paths', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+
+ var blocks = helpers.blocks();
+ var file = helpers.createFile('foo', 'app', blocks);
+
+ function resolveSource(sourceUrl) {
+ if (sourceUrl === 'foo.js') {
+ return path.join('missing', 'foo.js');
+ }
+ return null;
+ }
+ var c = new ConfigWriter(flow, {
+ root: 'app',
+ dest: 'dist',
+ staging: '.tmp'
+ }, {
+ resolveSource: resolveSource
+ });
+
+ helpers.file.mkdir('app');
+ helpers.file.write(path.join('app', 'bar.js'), 'var a=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var a=1;');
+
+ // mock grunt.fail.warn
+ helpers.mockGruntFailWarn(this);
+
+ c.process(file);
+ assert.ok(/returned non-existent path "missing[\\\/]foo.js"/.test(this.warnMessage));
+
+ // restore grunt.fail.warn
+ helpers.restoreGruntFailWarn();
+ });
+
+ it('should cancel normal search when it returns `false`, and invoke normal search when it returns `null`', function () {
+ var flow = new Flow({
+ steps: {
+ js: ['concat', 'uglify']
+ }
+ });
+
+ var blocks = helpers.blocks();
+ var file = helpers.createFile('foo', 'app', blocks);
+ var queue = [];
+
+ function resolveSource(sourceUrl) {
+ queue.push(sourceUrl);
+ if (sourceUrl === 'baz.js') {
+ return false;
+ }
+ return null;
+ }
+ var c = new ConfigWriter(flow, {
+ root: 'app',
+ dest: 'dist',
+ staging: '.tmp'
+ }, {
+ resolveSource: resolveSource
+ });
+
+ helpers.file.mkdir('app');
+ helpers.file.write(path.join('app', 'foo.js'), 'var a=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var a=1;');
+ helpers.file.write(path.join('app', 'baz.js'), 'var a=1;');
+
+ // mock grunt.fail.warn
+ helpers.mockGruntFailWarn(this);
+
+ c.process(file);
+ assert.ok(/can't resolve source reference "baz.js"/.test(this.warnMessage));
+
+ // restore grunt.fail.warn
+ helpers.restoreGruntFailWarn();
+
+ assert.deepEqual(queue, ['foo.js', 'bar.js', 'baz.js']);
+ });
+ });
});
diff --git a/test/test-file.js b/test/test-file.js
index 5c8119e..76d417d 100644
--- a/test/test-file.js
+++ b/test/test-file.js
@@ -36,9 +36,9 @@ describe('File', function () {
assert.equal(3, b1.raw.length);
assert.equal('css', b1.type);
assert.equal(1, b1.src.length);
- assert.equal(16, b2.raw.length);
+ assert.equal(4, b2.raw.length);
assert.equal('js', b2.type);
- assert.equal(13, b2.src.length);
+ assert.equal(2, b2.src.length);
});
it('should also detect block that use alternate search dir', function () {
diff --git a/test/test-usemin.js b/test/test-usemin.js
index 878883c..a47435f 100644
--- a/test/test-usemin.js
+++ b/test/test-usemin.js
@@ -34,6 +34,24 @@ describe('usemin', function () {
describe('absolute paths', function () {
beforeEach(directory('temp'));
+ it('should warn user if no html file found', function () {
+ grunt.log.muted = true;
+ grunt.config.init();
+ grunt.config('usemin', {
+ html: 'build/foo.html'
+ });
+
+ // mock grunt.fail.warn
+ helpers.mockGruntFailWarn(this);
+
+ grunt.task.run('usemin');
+ grunt.task.start();
+
+ // restore grunt.fail.warn
+ helpers.restoreGruntFailWarn();
+ assert.ok(this.warnMessage, 'No files found for target html');
+ });
+
it('should replace with revved files when found', function () {
grunt.file.mkdir('build');
grunt.file.mkdir('build/images');
@@ -367,6 +385,15 @@ describe('useminPrepare', function () {
html: 'index.html'
});
grunt.file.copy(path.join(__dirname, 'fixtures/usemin.html'), 'index.html');
+ grunt.file.copy(path.join(__dirname, 'fixtures/style.css'), 'styles/main.css');
+ grunt.file.mkdir('scripts');
+ grunt.file.write('scripts/bar.js', 'bar');
+ grunt.file.write('scripts/baz.js', 'baz');
+ grunt.file.mkdir('scripts/vendor');
+ grunt.file.mkdir('scripts/vendor/bootstrap');
+ grunt.file.write('scripts/vendor/bootstrap/bootstrap-affix.js', 'bootstrap-affix');
+ grunt.file.write('scripts/vendor/bootstrap/bootstrap-alert.js', 'bootstrap-alert');
+
grunt.task.run('useminPrepare');
grunt.task.start();
@@ -377,7 +404,7 @@ describe('useminPrepare', function () {
assert.equal(concat.generated.files.length, 2);
assert.equal(concat.generated.files[1].dest, path.normalize('.tmp/concat/scripts/plugins.js'));
- assert.equal(concat.generated.files[1].src.length, 13);
+ assert.equal(concat.generated.files[1].src.length, 2);
assert.equal(concat.generated.files[0].dest, path.normalize('.tmp/concat/styles/main.min.css'));
assert.equal(concat.generated.files[0].src.length, 1);
@@ -394,6 +421,11 @@ describe('useminPrepare', function () {
html: 'index.html'
});
grunt.file.copy(path.join(__dirname, 'fixtures/alternate_search_path.html'), 'index.html');
+ grunt.file.mkdir('build');
+ grunt.file.mkdir('build/scripts');
+ grunt.file.write('build/scripts/bar.js', 'bar');
+ grunt.file.write('build/scripts/baz.js', 'baz');
+
grunt.task.run('useminPrepare');
grunt.task.start();
@@ -407,6 +439,26 @@ describe('useminPrepare', function () {
assert.deepEqual(uglify.generated.files[0].src, [path.normalize('.tmp/concat/scripts/foo.js')]);
});
+ it('should warn user if no html file found', function () {
+ grunt.log.muted = true;
+ grunt.config.init();
+ grunt.config('useminPrepare', {
+ html: 'foo.html'
+ });
+
+ // mock grunt.fail.warn
+ helpers.mockGruntFailWarn(this);
+
+ grunt.task.run('usemin');
+ grunt.task.start();
+
+ grunt.task.run('useminPrepare');
+ grunt.task.start();
+
+ // restore grunt.fail.warn
+ helpers.restoreGruntFailWarn();
+ assert.ok(this.warnMessage, 'No source file found.');
+ });
it('output config for subsequent tasks should be relative to observed file', function () {
grunt.log.muted = true;
@@ -653,6 +705,16 @@ describe('useminPrepare', function () {
});
grunt.file.copy(path.join(__dirname, 'fixtures/usemin.html'), 'index.html');
+
+ grunt.file.copy(path.join(__dirname, 'fixtures/style.css'), 'styles/main.css');
+ grunt.file.mkdir('foo');
+ grunt.file.write('foo/scripts/bar.js', 'bar');
+ grunt.file.write('foo/scripts/baz.js', 'baz');
+ grunt.file.mkdir('foo/scripts/vendor');
+ grunt.file.mkdir('foo/scripts/vendor/bootstrap');
+ grunt.file.write('foo/scripts/vendor/bootstrap/bootstrap-affix.js', 'bootstrap-affix');
+ grunt.file.write('foo/scripts/vendor/bootstrap/bootstrap-alert.js', 'bootstrap-alert');
+
grunt.task.run('useminPrepare');
grunt.task.start();