I am using Karma to run tests against some code.
Both the tests and the code are transpiled (ES6 => ES5 using babel) before being run by Karma.
This works great and the tests run fine.
But if I try and use the text! plugin from any of the files being tested...
import template from 'text!./template.html';
...I get:
There is no timestamp for /base/src/text.js!
Uncaught Error: Script error for "text", needed by: text!app/template.html_unnormalized2
http://requirejs.org/docs/errors.html#scripterror
Uncaught Error: Load timeout for modules: text!app/template.html_unnormalized2
Does anyone have any ideas why this might be?
The built artifact in the dist folder (i.e. the item under test) contains the successfully encoded text RequireJS items eg:
define('text!app/template.html',[],function () { return '<div>foo</div>';});
Additional Info
test-main.js
var TEST_REGEXP = /(spec|test)\.js$/i;
var allTestFiles = [];
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
allTestFiles.push(normalizedTestModule);
}
});
require.config({
baseUrl: '/base/src',
paths: {},
shim: {},
deps: allTestFiles,
callback: window.__karma__.start
});
karma.conf.js
module.exports = function(config) {
'use strict';
var path = require('path');
var cdn = 'http://localhost:55635/modules/';
var basePath = path.dirname(__filename);
config.set({
basePath: '../../..',
frameworks: [
'requirejs',
'jasmine'
],
files: [
{
pattern: path.join(basePath, 'test-transpiled', '*-spec.js'),
included: false
},
path.join(basePath, 'dist', 'artifacts', 'app.js'),
path.join(basePath, 'test', 'unit', 'test-main.js')
],
proxies: {
'/cdn/': cdn
},
exclude: [],
preprocessors: {},
reporters: ['dots'],
colors: true,
autoWatch: false,
singleRun: false,
browsers: ['Chrome'],
});
};
Edit:
I have added the following to karma.conf.js:
files: [
{
pattern: path.join(basePath, 'node_modules/require-plugins/text/text.js'),
included: false
},
// ...
],
I continue to get an error when running the tests:
There is no timestamp for /base/src/text.js!
Presumably because I need to add "text" to the paths section of test-main.js?
require.config({
baseUrl: '/base/src',
paths: {
'text': '../node_modules/require-plugins/text/text'
},
// ...
});
But I have tried various combinations of baseUrl and the path in the text path and I cannot get it to stop 404-ing.
Your files option in karma.conf.js does not include the text plugin, which is why you get the error that there's no timestamp for it.
Add an item to your files list that hits the text plugin on your file system, and make sure that you have included: false for it. RequireJS plugins are like other modules: RequireJS must be able to load them to use them.
You may need to also set paths in test-main.js depending on where you put your plugin. RequireJS already is looking for it at /base/src/text.js. If you locate it so that the plugin is served at this URL, then there's no need to set paths. If you put it somewhere else, then you do need to set paths. Something like:
paths: {
text: 'path/to/text',
}
Remember that the paths in paths are interpreted relative to your baseUrl setting.
I tried using the require.js text! plugin, and also found that it conflicts with the baseUrl defined for the rest of the project.
The requirejs.config sets the baseUrl to the parent directory of the JS files, while my templates are defined in a sibling directory to the js.
There was no way to tell requirejs to load templates from /base/templates and js from base/js.
My solution was to change the text.js plugin, and add a hook to change the url just before the ajax call is made to fetch the HTML file. You can take my version of text.js from here.
Related
I'm trying to unit test an AngularJS/Browserify application via karma-browserify. Ultimately, when I run my gulp karma task, I get the error Error: [$injector:nomod] Module 'myApp' is not available! You either misspelled the module name or forgot to load it...
My gulpfile.js has the task
gulp.task('test', function(done) {
new karma.Server({
configFile: __dirname + '/karma.conf.js'
}, done).start();
});
My karma.conf.js includes
{
// ...
frameworks: ['browserify', 'jasmine'],
files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'spec/**/*.js'
],
preprocessors: {
'spec/**/*.js': [ 'browserify' ]
},
browserify: {
debug: true
}
// ...
}
I define my module in a main.js that includes
require('angular').module('myApp', [
//...lots of `require`s for each dependency...
]);
I define my controller in a MainCtrl.js that looks like
function MainCtrl(/*...deps...*/) {
var ctrl = this;
ctrl.foo = 'bar';
}
module.exports = MainCtrl;
then register the controller elsewhere like
var app = require('angular').module('myApp');
app.controller('MainCtrl', [/*...deps...*/, require('./MainCtrl')]);
Finally my test looks like
(function() {
"use strict";
describe('MainCtrl', function() {
var ctrl;
beforeEach( angular.mock.module('myApp') );
beforeEach( inject( function($controller) {
ctrl = $controller('MainCtrl');
}));
it('should be defined', function() {
expect(ctrl).toBeDefined();
});
});
}());
Workaround
The workaround I have is to add my main.js file to karma.conf.js
files: [
'js/main.js', // ADDED: Defines the app and `require`s angular
'node_modules/angular-mocks/angular-mocks.js',
'spec/**/*.js'
],
preprocessors: {
'js/main.js': ['browserify'], // ADDED
'spec/**/*.js': ['browserify']
}
and everything works. But I thought I wasn't supposed to add my source files to karma (at least not with karma-browserify). Is this the right way to set up my project?
Yes, the 'workaround' is the desired way to use karma-browserify.
preprocessors definition specifies which of the included files should be processed by which preprocessor but does not include them:
The keys of the preprocessors config object are used to filter the
files specified in the files configuration.
it is files definition that actually includes the files:
The files array determines which files are included in the browser and
which files are watched and served by Karma.
Files tells Karma which files it should load relative to the base path. These are:
All test related libraries
Our source code to test
The tests themselves
I am attempting to write karma tests, some of which require jquery. I am using requirejs.
Here is the relevant part of my karma.conf.js
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: [
'spec/test-main.js',
//tests
{pattern: 'spec/**/*.js', included: true},
//application files
{pattern: 'apps/**/*.js', included: false},
//jquery
{pattern: 'bower_components/jquery/src/*.js', included: false, served: true}
],
...
And my test-main.js:
var tests = [];
for (var file in window.__karma__.files) {
if (window.__karma__.files.hasOwnProperty(file)) {
if (/spec\.js$/.test(file)) {
tests.push(file);
}
}
}
requirejs.config({
// Karma serves files from '/base'
baseUrl: '/base',
paths: {
'jquery': 'bower_components/jquery/src/jquery'
},
map: {
'*': {
'jquery' : 'jquery'
}
},
shim: {
'jquery': {
exports: '$'
}
},
// ask Require.js to load these files (all our tests)
deps: tests,
// start test run, once Require.js is done
callback: window.__karma__.start
});
As is, I get:
ReferenceError: $ is not defined
for every spot I try to use jQuery. I also get, in the browser:
There is no timestamp for /base/(myPath).js!
Where (myPath) is the path to my file under test, which I find odd because that file resides under the path specified for application files in karma.conf.js.
If I take out the jquery tests, the other tests will execute and succeed.
If I change the "included" property to true in my karma.conf file list for jquery, I will get the following error:
Uncaught Error: Mismatched anonymous define()
If I attempt to add 'jquery' to the frameworks array in karma.conf, karma won't even start.
If I try to wrap my tests with some require shenanigans like require(['jquery'], function($) { the tests will not run and I'll get
Executed 0 of 0 ERROR.
I've also tried using define(function (require) {var $ = require('jquery'); which just gives me another
Uncaught Error: Mismatched anonymous define()
var $ = require('jquery') results in:
Uncaught Error: Module name "jquery" has not been loaded yet for context: _. Use require([])
Anyway, I've done quite a bit of searching around for similar problems and any solutions and I haven't been able to get this to work. Ideally, jquery would just be available for all my tests via karma, but even being able to require it manually in my test itself would be fine at this point. Also, not getting the no timestamp error would be nice. Any advice?
Thanks.
EDIT:
After a lot of trial and error, I think I've made some progress towards the goal, though I'm not quite there yet.
karma.conf.js
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: [
//jquery
{pattern: 'node_modules/jquery/src/*.js', included: false},
{pattern: 'node_modules/jquery/src/**/*.js', included: false},
//application files
{pattern: 'apps/**/*.js', included: false},
//tests
{pattern: 'spec/**/*.js', included: true},
'test-main.js'
],
...
Changed the order of the files here and added sub-directories of jquery. The real change came below in test-main.js:
var tests = [];
for (var file in window.__karma__.files) {
if (window.__karma__.files.hasOwnProperty(file)) {
if (/spec\.js$/.test(file)) {
tests.push(file);
}
}
}
requirejs.config({
// Karma serves files from '/base'
baseUrl: '/base',
paths: {
'jquery': 'node_modules/jquery/src/jquery'
},
map: {
'*': {
'jquery' : '/base/node_modules/jquery/src/jquery.js'
}
},
shim: {
},
// ask Require.js to load these files (all our tests)
deps: tests,
// start test run, once Require.js is done
callback: window.__karma__.start
});
Apparently when I previously had jquery in the shim object, that was wrong, and it was hiding a bunch of 404s. I am now getting a 404 for all the jquery dependencies, because jquery depends them via relative paths - requireJS does not append .js onto files required via relative paths.
So now I just need a way to tell requirejs to append the .js to the dependencies (other than brute-forcing it into every jquery file), and then hopefully this will be resolved. Anyone know how to do that?
EDIT 2: I skipped the middleman and just downloaded jquery-2.2.2.min rather than bother with a module version.
There were other hurdles I needed to clear as well: the order of the files as you include them in karma.conf.js matters, and in test-main.js, apparently only files with spec in the file name were being included as tests. I also needed to set all those 'included' properties to false.
Finally, in my case, my module that I am testing is defined using a string that's slightly different from the path karma is finding it by. Example:
//addition-functions.js
define('myModules/calculator/addition-functions', [], function () {
and in the test file:
//test-file.js
define(['jquery', 'myModules/calculator/addition-functions'], function($, addFunctions) {
If I did only this, I would 404 on my module, because it expects it to be within the apps folder. However, I cannot add apps to the path directly. I must use the paths variable in my test-main.js, like so:
requirejs.config({
// Karma serves files from '/base'
baseUrl: '/base',
paths: {
'jquery': 'lib/jquery-2.2.2.min',
'myModules/calculator/addition-functions' : 'apps/myModules/calculator/addition-functions'
},
...
This got things working. Will mark this as solved.
I am using on a backbonejs project with requirejs. I am using Gruntjs as build process. In this project I am using external underscore templates. Below is my dir structure.
MainApp/
app/
images/
js/
styles/
templates/
index.html
Below is my requirejs options in Gruntfile.js
requirejs: {
compile: {
options: {
name: "views/app",
baseUrl: "prod/js",
mainConfigFile: "prod/js/main.js",
out: "prod/scripts/scripts.min.js",
include: ['libs/requirejs/require.js']
}
}
}
However, this seems to be not working. When I build it by running grunt command it does build the project successfully i.e. I am not getting any errors during build process. But when I want to run this project in browser, it does not work. It shows the home page correctly with correct styles but javascript functionality is not working. One of the reason I can think of is I am using external templates which grunt requirejs plugin seems to be not picking up.
How can I use external templates?
UPDATE
I am using grunt-contrib-requirejs plugin.
RequireJS config file is nto included in output file. You must split config and main app:
src/js/config.js
/*global require:false */
require.config({
urlArgs: 'version=' + (new Date()).getTime(),
paths: {
'jquery' : 'libs/jquery/dist/jquery',
'underscore' : 'libs/underscore/underscore',
'backbone' : 'libs/backbone/backbone',
'localStorage' : 'libs/backbone.localStorage/backbone.localStorage',
'text' : 'plugins/text'
}
});
src/js/main.js
/*global require:false */
/*global Backbone:false */
/*global _:false */
require(['views/app', 'collections/todos', 'router'], function (AppView, TodoCollections, Router) {
window.App = {
Vent: _.extend({}, Backbone.Events)
};
new AppView({
collection: new TodoCollections()
});
window.App.TodoRouter = new Router();
Backbone.history.start();
});
Gruntfile.js
requirejs: {
compile: {
options: {
baseUrl: "dist/js",
mainConfigFile: "dist/js/config.js",
name: 'main',
out: "dist/scripts/scripts.min.js",
include: 'libs/requirejs/require.js',
optimize: 'none',
preserveLicenseComments: false,
useStrict: true,
wrap: true
}
}
},
I'm trying to modularize my existing project by breaking out functionality into separate applications that share a lot of common code. It's a Backbone/Marionette app, and everything is working fine in development mode, but I'm having trouble getting optimization to work. I currently have two pages, with 2 main files and 2 application files. The main files both contain requirejs.config blocks which are almost identical, except the second one uses the map config option to map the app module to loginApp. The reason for this is that most of the other modules depend on the app module for some application-wide functionality, including messaging and some global state variables.
main.js:
requirejs.config({
shim: { ... },
paths: { ... }
});
define(['vendor'], function() {
// This loads app.js
require(['app'], function(Application) {
Application.start();
});
});
main-login.js:
requirejs.config({
shim: { ... },
paths: { ... },
map: {
"*": { "app": "loginApp" }
}
});
define(['vendor'], function() {
// This loads loginApp.js because of the mapping above
require(['app'], function(Application) {
Application.start();
});
});
This works great until I optimize. I'm getting an error about a missing file, but having worked with requirejs long enough, I know that really has nothing to do with the problem. :)
From the docs:
Note: when doing builds with map config, the map config needs to be
fed to the optimizer, and the build output must still contain a
requirejs config call that sets up the map config. The optimizer does
not do ID renaming during the build, because some dependency
references in a project could depend on runtime variable state. So the
optimizer does not invalidate the need for a map config after the
build.
My build.js file looks like this:
({
baseUrl: "js",
dir: "build",
mainConfigFile: "js/main.js",
removeCombined: true,
findNestedDependencies: true,
skipDirOptimize: true,
inlineText: true,
useStrict: true,
wrap: true,
keepBuildDir: false,
optimize: "uglify2",
modules: [
{
name: "vendor"
},
{
name: "main",
exclude: ["vendor"]
},
{
name: "main-login",
exclude: ["vendor"],
override: {
mainConfigFile: "js/main-login.js",
map: {
"*": {
"app": "loginApp"
}
}
}
}
]
});
I'd like to avoid having 2 separate build files, if possible, and I'm working on breaking out the requirejs.config block into a single, shared file and having the 2 main files load that and then load the app files (this is similar to how the multipage example works) but I need that map config to work in the optimizer in order for this to work. Any ideas what I'm missing here?
Update
I've split out the config into its own file, config.js, which gets included by the main-* files. In the main-login.js file, I include the map config above the define and everything works in development mode.
require.config({
map: {
"*": {
"app": "loginApp"
}
}
});
define(['module', 'config'], function(module, config) {
...
The build.js file is the same as above, except with the second mainConfigFile removed. Optimization still fails, though. What I think is happening is, since this is a Marionette app, it's common practice to pass the Application object as a dependency to other parts of the app, including views, controllers and models. When I optimize, I run into two different problems. If I leave removeCombined as true, the optimizer will build in the dependencies from the first app, then remove those files, so when it sees them in the second app, it will fail because it can't find the source files anymore. Setting this to false seems reasonable, but the problem is this then gives me the following error:
Error: RangeError: Maximum call stack size exceeded
I can't find any consistent information on this particular error. It might have something to do with the hbs plugin (similar to text but for pre-compiling Handlebars templates) but I'm not positive that's the case. Since there's no stack trace, I'm not sure where to start looking. My gut feeling is it's a circular dependency somewhere, though. So, my updated question is, how should a multi-page Marionette app be decoupled so as to make sharing code (not just 3rd party code, but custom code such as data models and views) possible? Do I need to remove any dependencies on the core Application object? (That would require an awful lot of refactoring.) Since it works just fine in development mode, is there some trick to r.js's config I'm overlooking? I've tried adding app to the exclude lists as well as stubModules but nothing seems to work. I'm thinking of just creating 2 build files and being done with it, but I'd really like to know how to solve this the "right" way.
Your build file can be like this:
({
...
include: [ './config/main.js' ],
pragmasOnSave: {
excludeBuildConfig: true
}
})
You can use pragmasOnSave to tell optimizer to exclude a section in a file in optimized result, so Requirejs config file can be like following code
requirejs.config({
//>>excludeStart('excludeBuildConfig', pragmas.excludeBuildConfig)
shim: { ... },
paths: { ... },
//>>excludeEnd('excludeBuildConfig')
map: {
"*": { "app": "loginApp" }
}
});
The final solution used was to incorporate Grunt into the build workflow. Inside Grunt, I'm dynamically creating task targets to the requirejs task. I refactored my multiple applications to all use the same folder structure, so it was easy to reuse the same build config for each. There's still the minor inconvenience of compiling the vendor file multiple times, but that's a small price to pay.
Here's the function I use to create the config inside my dev task, in case anyone's interested:
var buildRequireTargets = function(appList) {
var requireTargets = {},
buildConfig = {
baseUrl: "<%= sourceDir %>/js",
dir: "<%= buildDir %>/js",
mainConfigFile: "<%= sourceDir %>/js/config.js",
removeCombined: true,
findNestedDependencies: true,
skipDirOptimize: true,
inlineText: true,
useStrict: true,
wrap: true,
keepBuildDir: true,
optimize: "none",
pragmasOnSave: {
excludeHbs: true
}
};
_.each(appList, function (app) {
requireTargets[app] = {
options: _.extend({
map: {
"*": {
"app": app + "/app"
}
},
modules: [
{
name: "vendor"
},
{
name: app + "/main",
exclude: ["vendor"]
}
]
}, buildConfig)
};
});
grunt.config("requirejs", requireTargets);
};
Following this RequireJS example I'm trying to have a single file for all vendor js assets like jquery and foundation, whilst loading page specific code in other modules.
While I can build and copy the js successfully (using grunt requirejs optimiser) into a build folder, the baseUrl in the require.config is obviously now wrong.
baseUrl: 'js' points to public/js instead of public/build/js and so all paths are incorrect.
Is there a way of dynamically updating the baseUrl when running the optimiser? So it points to public/build/js?
Here's my grunt task:
requirejs: {
dist: {
options: {
baseUrl: '<%=pkg.paths.js%>',
dir:'project/public/build/js',
mainConfigFile: '<%=pkg.paths.js%>main.js',
optimize: 'none',
removeCombined: true,
uglify2: {
no_mangle: true,
compress: {
drop_console: true
}
},
modules: [
{
name: 'vendorCommon'
},
{
name: 'dashboard',
exclude: ['vendorCommon']
}
]
}
}
}
Vendor Common
define(['jquery', 'foundation'],
function () {
//Just an empty function, this is a place holder
//module that will be optimized to include the
//common depenendencies listed in this module's dependency array.
});
Require Config
require.config({
baseUrl: '/js',
priority: ['vendorCommon'],
paths: {
'vendorCommon':'vendor/vendor-common',
'jquery':'../bower_components/jquery/dist/jquery.min',
'foundation':'../bower_components/foundation/js/foundation/foundation',
'dashboard':'views/dashboard'
},
shim: {
'foundation': ['jquery']
}
});
I've used the optimizer's onBuildWrite setting to modify some modules when they are optimized. If your configuration is included in your optimized bundle then you could use onBuildWrite to patch it:
onBuildWrite: function (moduleName, path, contents) {
if (moduleName === '<%=pkg.paths.js%>main') {
return contents.replace("baseUrl: '/js'", "baseUrl: '/build/js'");
}
return contents;
}
Disclaimer: I'm writing this off the top of my head. Beware of typos.
Another possibility would be to override baseUrl at runtime. RequireJS is able to combine multiple configurations into a single configuration. A new value of baseUrl in a later call would override the earlier value. So if you have a way to set up the optimized version of your app (for instance, through different HTML served by your server) to call require.config({baseUrl: '/build/js'}); after the call you show in your question but before any code that needs the correct baseUrl, this could also be an option.