diff --git a/HACKING.md b/HACKING.md index 5b0c53af..8caf1709 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,25 +1,60 @@ -# How to debug Jester +# How to debug jester -You attach a graphical debugger ([node-inspector]) to jester. +You attach a graphical debugger ([Node Inspector]) to jester. -- Install [node-inspector] somewhere. Doesn't matter where. +- Install [Node Inspector] somewhere. Doesn't matter where. $ npm install node-inspector -- Start node-inspector. - - $ node_modules/.bin/node-inspector - Node Inspector v0.7.4 - Visit http://127.0.0.1:8080/debug?port=5858 to start debugging. - - *node-inspector* now waits for the process-to-debug on port 5858, and for you on port 8080. - - Start jester in debug mode. - $ node --debug-brk node_modules/jester-tester/src/bin/jester-watch.js + $ node-debug node_modules/jester-tester/src/bin/jester-watch.js debugger listening on port 5858 -- Browse to http://127.0.0.1:8080/debug?port=5858. +- Node Inspector opens http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in a browser. - Place your breakpoints and press the play button to start running jester. -[node-inspector]: https://npmjs.org/package/node-inspector +[Node Inspector]: https://npmjs.org/package/node-inspector + +# Suppressing webpack errors/warnings + +Webpack can return three kinds of alerts (http://webpack.github.io/docs/node.js-api.html#error-handling): + +- Fatal errors - webpack was not able to build. +- Soft errors - webpack was able to build, but it'll probably break if you try to run it. +- Warnings - webpack was able to build and it'll probably work, but there are some code paths that break. + +Some soft errors / warnings are false positives, +in the sense that the scenario in which these cause a failure is guaranteed never to occur in practice. + +You can tell jester to suppress these false positives by adding the following to your `jester.json`. + +```json +{ + ... + "webpackAlertFilters": [ + { + "severity": "softError" or "warning", + "name": "ModuleNotFoundError", + "justification": "Suppressing this alert is a good idea because ...", + "origin/rawRequest": "imports?process=>undefined!when", + "dependencies/0/request": "vertx" + }, + { + "severity": "softError" or "warning", + "name": "CriticalDependenciesWarning", + "justification": "Suppressing this alert is a good idea because ...", + "origin/rawRequest": "localforage", + "origin/blocks/0/expr/type": "CallExpression" + } + ] +} +``` + +Sadly, figuring out exactly which values to configure is not easy: +they're not visible in jester's console output. +To get a hold of them, [debug jester](#how-to-debug-jester) and place a breakpoint in `src/lib/handleWebpackResult.js`. +Then build some code that triggers the alert that you wish to suppress. +This will hit the breakpoint. +Inspect the contents of `stats.compilation.errors` and/or `stats.compilation.warnings` +to see which values you should add to your configuration. diff --git a/README.md b/README.md index 6f5c03b6..c354ed1d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +**NOTE: you probably want the @next version of jester tester!** + +You can install it using `npm install jester-tester@next`. I've been working on it for a while now and it has a bunch of needed improvements such as being able to configure webpack and eslint directly. However I'm currently beta testing it and I have yet to add a migration script. Eslint is doing a lot of breaking changes at the moment while they're ramping up for their 1.0 release so I won't make the @next version the default until after the 1.0 eslint release. I also might replace karma with testem before that. + +But me and my team are using the @next version full time, so it probably won't delete your entire file system. I encourage new jester users to start with the @next version. Just be aware that you might have a non-automatic upgrades (usually changing some settings, renaming some properties) for new releases. + +If you encounter anything: let me know at https://github.com/jauco/jester/issues ! + + #Jester >Get your project tested and out there with minimal fuss. @@ -15,7 +24,7 @@ The goal of Jester is to give you a bootstrap for integrating these tools so you 1. Install [node.js](http://nodejs.org/download/) 2. Create a directory for your app `mkdir myApp; cd myApp` 3. Add a basic machine readable description of your app `npm init` - 4. Install jester from npm and save it into the development dependencies. Under windows you'll have to run this as administrator (required by jsdoc for creating symlinks): + 4. Install jester from npm and save it into the development dependencies. Under windows you'll have to run this as administrator (required by jsdoc for creating symlinks): `npm install --save-dev jester-tester` ## Creating a project @@ -127,7 +136,7 @@ describe("Greetings", function() { ``` This test asserts that the function hello will return the string `hello tested world`. -The test will run upon saving it if jester-watch is running, or else when you +The test will run upon saving it if jester-watch is running, or else when you execute jester-batch. Jester will now run your tests in the browsers as specified in `jester.js`. Of @@ -144,7 +153,7 @@ module.exports = hello; ##Dependency injection Jester makes it easy to replace a 'require'd module in the source file with a -test-stub. +test-stub. Let's say we have a file called src/lib/db.js ```javascript @@ -196,7 +205,7 @@ what happens under the hood. It's not really magical. ## Generating documentation from source -Documentation will be generated from appropriately annotated sources by jsdoc and includes the syntax highlighted source code. See [usejsdoc](http://usejsdoc.org/) for how to annotate your code, especially relevant is [Document CommonJS Modules](http://usejsdoc.org/howto-commonjs-modules.html). +Documentation will be generated from appropriately annotated sources by jsdoc and includes the syntax highlighted source code. See [usejsdoc](http://usejsdoc.org/) for how to annotate your code, especially relevant is [Document CommonJS Modules](http://usejsdoc.org/howto-commonjs-modules.html). There are many ways to export and document code. The recommended way to export functions is: @@ -204,7 +213,7 @@ There are many ways to export and document code. The recommended way to export f * Deep clone an object * * @param {Object} obj - the object to clone - * @returns {Object} a deep clone of the original object + * @returns {Object} a deep clone of the original object * @see {@link http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object} */ function clone(obj) { @@ -215,8 +224,8 @@ There are many ways to export and document code. The recommended way to export f This enables jsdoc to recognize that clone is a (static) function, the clone symbol will show up in stack traces and is fully supported by IE8. -The api documentation will be written to a directory specified by the `apiDocPath` setting in `jester.json`, which defaults to `./doc/api/`. You can set the configuration option `readme` to point to a file that is a markdown formatted readme which will be included in the generated documentation on the homepage. +The api documentation will be written to a directory specified by the `apiDocPath` setting in `jester.json`, which defaults to `./doc/api/`. You can set the configuration option `readme` to point to a file that is a markdown formatted readme which will be included in the generated documentation on the homepage. -The api documentation will be generated when you run `jester-batch` or `jester-doc`. The latter is a bit faster because it *only* runs jsdoc. The documentation is *not* automatically updated when running `jester-watch`. +The api documentation will be generated when you run `jester-batch` or `jester-doc`. The latter is a bit faster because it *only* runs jsdoc. The documentation is *not* automatically updated when running `jester-watch`. -An additional benefit of annotating your code with jsdoc style comments is that there are a number of tools such as ide's and compilers which can take advantages of the additional information contained in those comments. \ No newline at end of file +An additional benefit of annotating your code with jsdoc style comments is that there are a number of tools such as ide's and compilers which can take advantages of the additional information contained in those comments. diff --git a/src/bin/jester-batch.js b/src/bin/jester-batch.js index a45be401..cc65fe1f 100755 --- a/src/bin/jester-batch.js +++ b/src/bin/jester-batch.js @@ -11,7 +11,7 @@ var loadConfig = require("../lib/loadConfig"), var config = loadConfig(); -rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackWarningFilters) +rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackAlertFilters) .then(function() { if(config.srcPath && config.apiDocPath) { return rebuildDocumentation(config.srcPath, config.apiDocPath, config.jsdocConf, config.readme); diff --git a/src/bin/jester-watch.js b/src/bin/jester-watch.js index 5867ccf0..3ebea8d8 100755 --- a/src/bin/jester-watch.js +++ b/src/bin/jester-watch.js @@ -33,7 +33,7 @@ function runTests(path) { console.log("No tests found for '" + path + "'"); return false; } - return createTestFile(testFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackWarningFilters).then(function () { + return createTestFile(testFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackAlertFilters).then(function () { return server.run(); }); }); @@ -65,7 +65,7 @@ function startWatching() { } if (filePath.length > 3 && filePath.substr(-3) === ".js") { - var build = rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackWarningFilters); + var build = rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackAlertFilters); if (isReallyFileChangeEvent(changeType, fileCurrentStat, filePreviousStat)) { when.join(build, runTests(filePath)).done(function(){}); } else { diff --git a/src/lib/createTestFile.js b/src/lib/createTestFile.js index d8bc449c..ed1bc695 100644 --- a/src/lib/createTestFile.js +++ b/src/lib/createTestFile.js @@ -19,7 +19,7 @@ function createEntryModules(srcPath, filenames) { return entryModules; } -module.exports = function createTestFile(filenames, srcPath, webpackConfig, karmaPath, webpackWarningFilters) { +module.exports = function createTestFile(filenames, srcPath, webpackConfig, karmaPath, webpackAlertFilters) { var config = Object.create(webpackConfig); config.output = Object.create(webpackConfig.output); config.output.path = karmaPath; @@ -27,6 +27,6 @@ module.exports = function createTestFile(filenames, srcPath, webpackConfig, karm config.output = Object.create(config.output || {}); config.output.filename = "[name].karmatest.js"; return webpack(config).then(function(stats) { - return handleWebpackResult(stats, webpackWarningFilters); + return handleWebpackResult(stats, webpackAlertFilters); }); }; diff --git a/src/lib/handleWebpackResult.js b/src/lib/handleWebpackResult.js index f124e598..535b5a0f 100644 --- a/src/lib/handleWebpackResult.js +++ b/src/lib/handleWebpackResult.js @@ -1,8 +1,15 @@ "use strict"; -module.exports = function handleWebpackResult(stats, webpackWarningFilters) { +module.exports = function handleWebpackResult(stats, webpackAlertFilters) { if (stats) { - if (filterConfigIsValid(webpackWarningFilters)) { - stats.compilation.warnings = filterWebpackWarnings(stats.compilation.warnings, webpackWarningFilters); + if (webpackAlertFilters && filterConfigIsValid(webpackAlertFilters)) { + var softErrorFilters = webpackAlertFilters.filter(function isSoftErrorFilter(filter) { + return filter.severity === "softError"; + }); + var warningFilters = webpackAlertFilters.filter(function isWarningFilter(filter) { + return filter.severity === "warning"; + }); + stats.compilation.errors = filterWebpackAlerts(stats.compilation.errors, softErrorFilters); + stats.compilation.warnings = filterWebpackAlerts(stats.compilation.warnings, warningFilters); } stats = stats.toJson(); } @@ -18,7 +25,7 @@ module.exports = function handleWebpackResult(stats, webpackWarningFilters) { var logErrorsByLine = function (errors) { byLine(errors, function (err) { - console.log(" ", err, typeof err); + console.log(" ", err); }); }; @@ -43,78 +50,98 @@ module.exports = function handleWebpackResult(stats, webpackWarningFilters) { }; -function filterConfigIsSupported(webpackWarningFilter) { - return "name" in webpackWarningFilter - && "origin/rawRequest" in webpackWarningFilter - && "dependencies/0/request" in webpackWarningFilter - && Object.keys(webpackWarningFilter).length === 3 // That is, webpackWarningFilter contains no keys other than these three. - && webpackWarningFilter.name === "ModuleNotFoundError"; +function filterConfigIsSupported(webpackAlertFilter) { + if (webpackAlertFilter && webpackAlertFilter.name && webpackAlertFilter.justification) { + if (webpackAlertFilter.name === "ModuleNotFoundError") { + return "origin/rawRequest" in webpackAlertFilter + && "dependencies/0/request" in webpackAlertFilter; + } else if (webpackAlertFilter.name === "CriticalDependenciesWarning") { + return "origin/rawRequest" in webpackAlertFilter + && "origin/blocks/0/expr/type" in webpackAlertFilter; + } else { + return false; + } + } else { + return false; + } } /** - * Checks the sanity of the webpackWarningFilters-configuration. - * @param {Array} webpackWarningFilters - the filter configuration. + * Checks the sanity of the webpackAlertFilters-configuration. + * @param {Array} webpackAlertFilters - the filter configuration. * @return {bool} whether it is sane */ -function filterConfigIsValid(webpackWarningFilters) { - var result = true; - if (webpackWarningFilters) { - if (Array.isArray(webpackWarningFilters)) { - for (var i = 0; i < webpackWarningFilters.length; i++) { - if (!filterConfigIsSupported(webpackWarningFilters[i])) { - // This filter config isn't supported. Abort. - console.warn( - "config.webpackWarningFilters[" + i + "] must be an object similar to {\n" + - " 'name': 'ModuleNotFoundError',\n" + - " 'origin/rawRequest': 'imports?process=>undefined!when',\n" + - " 'dependencies/0/request': 'vertx'\n" + - "}." - ); - result = false; - } +function filterConfigIsValid(webpackAlertFilters) { + var result; + if (Array.isArray(webpackAlertFilters)) { + result = true; + for (var i = 0; i < webpackAlertFilters.length; i++) { + if (!filterConfigIsSupported(webpackAlertFilters[i])) { + // This filter config isn't supported. Abort. + console.warn( + 'config.webpackAlertFilters[' + i + '] must be an object similar to either\n' + + '{\n' + + ' "severity": "softError" or "warning",\n' + + ' "name": "ModuleNotFoundError",\n' + + ' "justification": "Suppressing this alert is a good idea because ...",\n' + + ' "origin/rawRequest": "imports?process=>undefined!when",\n' + + ' "dependencies/0/request": "vertx"\n' + + '}\n' + + 'or\n' + + '{\n' + + ' "severity": "softError" or "warning",\n' + + ' "name": "CriticalDependenciesWarning",\n' + + ' "justification": "Suppressing this alert is a good idea because ...",\n' + + ' "origin/rawRequest": "localforage",\n' + + ' "origin/blocks/0/expr/type": "CallExpression"\n' + + '}\n' + ); + result = false; } - } else { - console.warn("config.webpackWarningFilters must be an array."); - result = false; } } else { - // No filters are configured. That's okay. - result = true; + console.warn("config.webpackAlertFilters must be an array."); + result = false; } return result; } -function filterWebpackWarnings(unfilteredWarnings, webpackWarningFilters) { - if (!webpackWarningFilters) { - // No filters are configured. Use the complete, unfiltered list of warnings. - return unfilteredWarnings; +function filterWebpackAlerts(unfilteredAlerts, alertFilters) { + if (!alertFilters) { + // No filters are configured. Use the complete, unfiltered list of alerts. + return unfilteredAlerts; } // There's at least one filter and all filter configs are supported. Do the actual filtering. - var filteredWarnings = unfilteredWarnings.filter(function filterWarning(warning, index) { - if (warning.name !== "ModuleNotFoundError") { - // filterWebpackWarnings only supports filtering of ModuleNotFoundError-warnings. - // This is some other kind of warning. Leave it in the list, so that it IS shown in Jester's output. - return true; - } - - for (var i = 0; i < webpackWarningFilters.length; i++) { - var webpackWarningFilter = webpackWarningFilters[i]; - if ( - warning.origin.rawRequest === webpackWarningFilter["origin/rawRequest"] - && warning.dependencies.length > 0 - && warning.dependencies[0].request === webpackWarningFilter["dependencies/0/request"] - ) { - // This warning matches one of the filters. + var filteredAlerts = unfilteredAlerts.filter(function isShown(alert) { + for (var i = 0; i < alertFilters.length; i++) { + var webpackAlertFilter = alertFilters[i]; + var matches = matchers[webpackAlertFilter.name]; + if (matches(alert, webpackAlertFilter)) { + // This alert matches one of the filters. // Remove it from the list, so that it is NOT shown in Jester's output. return false; } } - // This warning matches none of the filters. + // This alert matches none of the filters. // Leave it in the list, so that it IS shown in Jester's output. return true; }); - return filteredWarnings; + return filteredAlerts; } + +var matchers = { + ModuleNotFoundError: function matches(alert, webpackAlertFilter) { + return alert.name === "ModuleNotFoundError" + && alert.origin.rawRequest === webpackAlertFilter["origin/rawRequest"] + && alert.dependencies.length > 0 + && alert.dependencies[0].request === webpackAlertFilter["dependencies/0/request"]; + }, + CriticalDependenciesWarning: function matches(alert, webpackAlertFilter) { + return alert.name === "CriticalDependenciesWarning" + && alert.origin.rawRequest === webpackAlertFilter["origin/rawRequest"] + && alert.origin.blocks[0].expr.type === webpackAlertFilter["origin/blocks/0/expr/type"]; + }, +}; diff --git a/src/lib/rebuildProject.js b/src/lib/rebuildProject.js index 705286b6..29260b5c 100644 --- a/src/lib/rebuildProject.js +++ b/src/lib/rebuildProject.js @@ -18,7 +18,7 @@ function createEntryModules(featureFiles) { return entryModules; } -module.exports = function rebuildProject(webpackConfig, entryGlob, webpackWarningFilters) { +module.exports = function rebuildProject(webpackConfig, entryGlob, webpackAlertFilters) { return glob(entryGlob) .then(function (featureFiles) { var config = Object.create(webpackConfig); @@ -26,6 +26,6 @@ module.exports = function rebuildProject(webpackConfig, entryGlob, webpackWarnin return webpack(config); }) .then(function (stats){ - return handleWebpackResult(stats, webpackWarningFilters); + return handleWebpackResult(stats, webpackAlertFilters); }); }; diff --git a/src/lib/runAllTests.js b/src/lib/runAllTests.js index 121f5aee..6765d776 100644 --- a/src/lib/runAllTests.js +++ b/src/lib/runAllTests.js @@ -17,7 +17,7 @@ module.exports = function runAllTests(config) { return getTestFiles(config.srcPath); }) .then(function (testInputFiles) { - return createTestFile(testInputFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackWarningFilters); + return createTestFile(testInputFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackAlertFilters); }) .catch(function(err) { console.error("failed creating test files ", err);