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();