Related
I have a nw.js native application with angular.js inside. My app bundled with webpack and contains native node.js modules. My entry point is index.js file that I organized like this:
var gui = require('nw.gui');
var angular = require('angular');
require('./app.css');
// other modules
var myApp = angular.module('myApp', [
'ngRaven',
'ngMaterial',
'ngMessages'
]).constant(
'fs', require('fs')
)
require('./services')(myApp);
require('./directives')(myApp);
require('./factories')(myApp);
require('./filters')(myApp);
require('./controllers')(myApp);
require('./app.js')(myApp);
My webpack config looks like this:
const path = require('path');
const config = {
entry: [
'./app/index.js'
],
output: {
path: path.resolve(__dirname, 'app'),
filename: 'bundle.js'
},
devtool: "source-map",
target: 'node-webkit',
module:{
// css, html loaders
},
node: {
os: true,
fs: true,
child_process: true,
__dirname: true,
__filename: true
}
};
module.exports = config;
So every dependency include Node.js native modules like fs, path, child_process bundled in one big file bundle.js that i include in html and then package my nw.js app. So my app structure looks like:
my_project:
--app
----controllers
------welcome
--------welcome.js // Page controller
--------welcome.html // Page HTML
------index.js // here I include each page controller
----app.js // My angular app initialization
----index.js // here I include all dependencies
I'm trying to run tests with this structure. I tried karma+jasmine, karma+mocha, tried different configurations, my last one looks like:
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'app/bundle.js',
'app/**/*spec.js'
],
exclude: [],
preprocessors: {
'app/bundle.js': ['webpack']
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadlessNoSandbox'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
singleRun: true,
webpack: {
// you don't need to specify the entry option because
// karma watches the test entry points
// webpack watches dependencies
// ... remainder of webpack configuration (or import)
},
webpackMiddleware: {
// webpack-dev-middleware configuration
// i.e.
noInfo: true,
// and use stats to turn off verbose output
stats: {
// options i.e.
chunks: false
}
}
});
};
But my tests still not see the angular application.
describe('Welcome page', function() {
beforeEach(angular.mock.module('WelcomePageCtrl'));
});
P.S I don't require exactly karma and jasminne, so any solution will be appreciated. I just want to cover my project with tests
I have gone through something similar myself. I don't think you need the bundle.js for your tests.
Here is how would do it:
I assume your controller/service/etc implementation is as follows:
/* app/controller/welcome.js */
module.exports = function(app) {
return app.controller("MyCtrl", function($scope) {
$scope.name = "lebouf";
});
};
I like my test code to sit right beside the code I'm testing (Welcome.spec.js in the same directory as Welcome.js). That test code would look like so:
/* app/controller/welcome.spec.js */
beforeEach(function() {
var app = angular.module("myApp", []); //module definition
require("./welcome")(app);
});
beforeEach(mockModule("myApp"));
describe("MyCtrl", () => {
var scope = {};
beforeEach(mockInject(function($controller) {
$controller("MyCtrl", {
$scope: scope
});
}));
it("has name in its scope", function() {
expect(scope.name).to.equal("not lebouf"); //the test will fail
});
});
Except, this is an angular controller we're testing and it's not that simple. We need the angular object itself. So lets set it up. I'll explain why it is done how it is done next:
/* test-setup.js */
const jsdom = require("jsdom").jsdom; //v5.6.1
const chai = require("chai");
global.document = jsdom("<html><head></head><body></body></html>", {});
global.window = document.defaultView;
global.window.mocha = true;
global.window.beforeEach = beforeEach;
global.window.afterEach = afterEach;
require("angular/angular");
require("angular-mocks");
global.angular = window.angular;
global.mockInject = angular.mock.inject;
global.mockModule = angular.mock.module;
global.expect = chai.expect;
console.log("ALL SET");
And we'll run the tests as:
node_modules/.bin/mocha ./init.js app/**/*.spec.js
#or preferably as `npm test` by copying the abev statement into package.json
extra info
Here is how init.js is setup as is:
jsdom: f you require("angular/angular") you'll see that it needs a window instance. jsdom can create documents and windows and so on without a web browser!
window.mocha: we need angular-mocks to populate our angular with the necessary utilities. But if you look at the code you'll notice that window.mocha || window.jasmine needs to be true. Thats whywindow.mocha = true`
window.beforeEach, window.afterEach: the same reason as above; because angular-mocks.js demands it.
I set some global variables that I plan to use commonly in my tests: angular, expect, mockInject, mockModule.
Also these may provide some additional information:
https://kasperlewau.github.io/post/angular-without-karma/
https://gist.github.com/rikukissa/dcb422eb3b464cc184ae
I have some repositories (A, B, C and TestRepo) all of them in local, i get karma to load spec files from repos A or B or C with a param (node script).
Now im trying to transpile the files cause some test are using import so i need babel. I configured babel as a preprocessor and it seems like it is working but know i get this error:
Uncaught Error: Module name "builder" has not been loaded yet for context: _. Use require([])
I tried all configs i know but still not working.
My karma.config.js is this:
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'requirejs'],
files: [
'test-main.js'
],
exclude: [
],
preprocessors: {
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
});
}
My test-main.js for requirejs:
var allTestFiles = []
var TEST_REGEXP = /(spec|test)\.js$/i
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',
deps: allTestFiles,
callback: window.__karma__.start
})
And my node file, where i set karma files, preprocessor and so on base on the repository i got from params is this:
const cfg = require('karma').config;
const stopper = require('karma').stopper;
const runner = require('karma').runner;
const Server = require('karma').Server;
const path = require('path');
const REPOSITORY = process.argv[2] || '';
let karmaConfig = cfg.parseConfig(path.resolve('./karma.conf.js'), { port: 9876 } );
let BASE_PATH;
if (REPOSITORY !== 'all') {
BASE_PATH = `/Users/fernando.delolmo/${REPOSITORY}`;
karmaConfig.files.push({pattern: BASE_PATH + '/test/**/*.spec.js'});
karmaConfig.files.push({pattern: BASE_PATH + '/src/**/*.js', included: true});
karmaConfig.preprocessors[BASE_PATH + '/src/**/*.js'] = ['babel'];
karmaConfig.preprocessors[BASE_PATH + '/test/**/*.spec.js'] = ['babel'];
karmaConfig.exclude.push(BASE_PATH + '/node_modules/**/*.spec.js');
} else {
BASE_PATH = '/Users/fernando.delolmo/';
karmaConfig.files.push({pattern: BASE_PATH + '**/test/**/*.spec.js'});
karmaConfig.files.push({pattern: BASE_PATH + '**/src/**/*.js', included: true});
karmaConfig.preprocessors[BASE_PATH + '**/src/**/*.js'] = ['babel'];
karmaConfig.preprocessors[BASE_PATH + '**/test/**/*.spec.js'] = ['babel'];
karmaConfig.exclude.push(BASE_PATH + '**/node_modules/**/*.spec.js');
}
karmaConfig.basePath = BASE_PATH;
var server = new Server(karmaConfig, function(exitCode) {
process.exit(exitCode);
});
server.start();
server.on('browser_start', function() {
runner.run(karmaConfig, function(exitCode) {
process.exit(exitCode);
});
});
server.on('browser_complete', function() {
stopper.stop(karmaConfig, function(exitCode) {
process.exit(exitCode);
});
});
Its my first time configuring karma, requirejs and babel all together so any help will be great :)
Okay finally i got what's the problem here. Just in case some one get here finding any solution to problems like this.
If you want to run test with ES2015 modules with karma, you will use babel to transpile into CommonJS modules at first, good! but... you will get errors like export is undefined.. and.. why?? Simple, export/import still not supported on the browsers, this means you CAN'T run CommonJS modules on the browser without any module bundler (browersify or webpack).
So, keep this clear:
1st -> Babel only transpile your ES2015 code and test into CommonJS modules
2nd -> Webpack / Browersify get CommonJS modules and bundle them into a bundle.js and then you can run that on the browser with karma.
I am trying to add testing to the website I'm building. I'm using Mocha as my testing framework and Chai and expect as my assertion library. I made a simple test just to make sure things work and then I created a Gruntfile to run my tests. The test is a simple test that just verifies that true === true and it worked both locally and on Travis CI. Now, even though I haven't changed anything in the test, it only works locally, but fails on Travis CI. It was passing before and it still passes locally, so I'm not sure what to change.
My simple test code looks like this:
'use strict';
var chai = require('chai');
var expect = chai.expect;
describe('Test that tests run', function(done) {
it('should run a test', function(done) {
expect(true).to.eql(true);
done();
});
});
My Gruntfile looks like this:
'use strict';
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-jscs');
// initialize Grunt
grunt.initConfig({
// create jshint task
jshint: {
dev: {
// tell jshint what check
src: ['Gruntfile.js', 'server.js', 'js/**/*.js', 'models/**/*.js', 'routes/**/*.js', '!build/**', '!tests/client/bundle.js', '!tests/karma_tests/bundle.js', '!js/imageMapResizer.min.js', '!js/kickstart.js', '!js/form-validator.js'],
options: {
node: true,
globals: {
describe: true,
it: true,
before: true,
after: true,
beforeEach: true,
afterEach: true,
res: true
}
}
},
mocha: {
// tell mocha where test files are
src: ['tests/**/*.js', '!tests/client/bundle.js', '!tests/karma_tests/bundle.js'],
options: {
node: true,
globals: {
describe: true,
it: true,
before: true,
after: true,
beforeEach: true,
afterEach: true,
res: true,
expect: true
}
}
},
// create jscs task
jscs: {
dev: {
// tell jscs to test the same files as jshint
src: ['<%= jshint.dev.src %>', '<%= jshint.mocha.src %>']
}
}
},
// create simplemocha task
simplemocha: {
dev: {
src: ['tests/test_entry.js']
}
}
});
// register linting task
grunt.registerTask('lint', ['jshint:dev', 'jshint:mocha' /*, 'jshint:jasmine'*/ ]);
// register mocha test task
grunt.registerTask('test', ['simplemocha:dev']);
grunt.registerTask('default', ['test']);
};
And .travis.yml looks like this:
language: node_js
node_js:
- "4.1"
- "4.0"
- "0.12"
- "0.11"
- "0.10"
- "0.8"
- "0.6"
- "iojs"
before_install:
- npm install -g grunt-cli
script: grunt test
Let me know if you have questions or want to see more code. Thanks in advance for all your help!
After some more digging with Travis CI I found there were 2 problems. The first was that node.js versions 0.6 and 0.8 were incompatible with many of my node packages. The other problem was 2 node packages I had included were not compatible with linux, which is the OS Travis CI uses to run tests. The packages were node-sqlserver-unofficial and msnodesqlv8.
I don't really need to work on node.js versions 0.6 or 0.8 and the I can work without the 2 node packages, so once I removed the the older node versions and the packages and my tests passed with flying colors.
I want to avoid duplicate code, so i am trying to load grunt task from Grunt file "a" and use them in gruntfile "b".
that means: i want to see all task of "a" in file "b" (but without code), just setup like a reference or template to another gruntfile.
here is grunt file "b":
module.exports = function (grunt) {
'use strict';
var karmaGrunt = './../../grunt',
abortHandler = function () {
var errors = grunt.fail.errorcount,
warnings = grunt.fail.warncount;
if (errors > 0 || warnings > 0) {
//run rocketlauncher python script and then stop the grunt runner.
grunt.task.run(["shell:rocketlauncher", "fatal"]);
}
},
fatal = function () {
// this function stops grunt and make the jenkins build red.
grunt.fail.fatal('failed');
};
require("grunt-load-gruntfile")(grunt);
// load grunt task from another file and add it.
grunt.loadGruntfile(karmaGrunt);
//grunt needs to continue on error or warnings, that's why we have to set the force property true
grunt.option('force', true);
grunt.initConfig({
shell: {
options: {
execOptions: {
cwd: '../scripts'
}
},
'rocketlauncher': {
command: './runRocketLauncher.sh'
}
}
});
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('build-process', ['karma', 'abortHandler']);
grunt.registerTask('abortHandler', abortHandler);
grunt.registerTask('fatal', fatal);
}
here is file "a":
module.exports = function (grunt) {
"use strict";
var eConfig = '../e-specs/karma.config.js',
dConfig = '../d-specs/karma.config.js',
cConfig = '../c-specs/karma.config.js';
grunt.initConfig({
karma: {
options: {
reporters: ['progress', 'coverage', 'threshold']
},
c: {
configFile: cConfig
},
d: {
configFile: dConfig
},
e: {
configFile: eConfig
}
}
});
grunt.loadNpmTasks('grunt-karma');
};
my file b load the task "Karma" but if i run only the grunt file of a i have 3 nested task ("e","c","d") but if i load them from another file, the only task i can see is "karma"
the error is:
No "karma" targets found.
Warning: Task "karma" failed. Used --force, continuing.
Done, but with warnings.
If i run the same task in file "a" directly the task is working like a charm.
There is a grunt plugin to load another Gruntfile: grunt-load-gruntfile
With this you can merge two Grunt configurations, including the defined tasks.
Here is an example:
./Gruntfile.js:
module.exports = function (grunt) {
require("grunt-load-gruntfile")(grunt);
grunt.loadGruntfile("web"); //loads the Gruntfile from the folder web/
grunt.registerTask('showConfig', "shows the current config", function(){
console.log(JSON.stringify(grunt.config(), null, 2));
});
};
and the second Gruntfile in ./web/Gruntfile.js.
module.exports = function (grunt) {
grunt.config("WebConfig", "Configuration from the Gruntfile in web/Gruntfile.js");
grunt.registerTask('server', "runs the server",function(){
console.log("just shows this message");
});
};
running grunt showConfig executes the task from the first Gruntfile and displays the configuration, including the parameter defined in ./web/Gruntfile.js.
running grunt server executes the task from ./web/Gruntfile.js.
Background: I am compiling 2 dependent TypeScript files to js, which produces also source maps (one source map per file) using tsc 1.0
I'm using -m commonjs and then use browserify to generate a single bundle.js
However I noticed that I get the original source map references twice in the bundle, which doesn't seem to work.
Passing --debug doesn't seem to do the trick either.
I had a feeling this issue: https://github.com/substack/node-browserify/issues/325 is somewhat related, but I couldn't figure out how the issue was resolved.
Also https://github.com/substack/browser-pack was suggested, but again I don't fully understand how to use it, is it a replacement to browserify?
Bottom line, I would like to merge the 2 js files but "merge" the js to ts source maps using browserify. Is that possible?
tsify is a browserify plugin that is better and replaces e.g. typescriptifier.
npm install tsify browserify watchify
You use tsify like this:
browserify src/index.ts -p tsify --debug -o build/index.js
Notice that this supports browserify --debug switch, no extra tricks required. So you can also use it with watchify like this:
watchify src/index.ts -p tsify --debug -o build/index.js
Using the minifyify browserify plugin I believe you can use TypeScript with Browserify and retain the source maps. After compiling the TypeScript files you should be able to pass the "entry" file (the one that imports the other one via commonjs syntax) through browserify with the minifyify plugin.
var browserify = require('browserify'),
bundler = new browserify();
bundler.add('entry.js');
bundler.plugin('minifyify', {map: 'bundle.js.map'});
bundler.bundle({debug: true}, function (err, src, map) {
if (err) console.log(err);
fs.writeFileSync('bundle.js', src);
fs.writeFileSync('bundle.js.map', map);
});
Here is my working solution:
var settings = {
projectName : "test"
};
gulp.task("bundle", function() {
var mainTsFilePath = "src/main.ts";
var outputFolder = "bundle/src/";
var outputFileName = settings.projectName + ".min.js";
var pkg = require("./package.json");
var banner = [
"/**",
" * <%= pkg.name %> v.<%= pkg.version %> - <%= pkg.description %>",
" * Copyright (c) 2015 <%= pkg.author %>",
" * <%= pkg.license %>",
" */", ""
].join("\n");
var bundler = browserify({
debug: true,
standalone : settings.projectName
});
// TS compiler options are in tsconfig.json file
return bundler.add(mainTsFilePath)
.plugin(tsify)
.bundle()
.pipe(source(outputFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(header(banner, { pkg : pkg } ))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(outputFolder));
});
I created example project.
You can run it with $(npm bin)/gulp build --env=dev for development environment and source maps will be generated.
There is gulpfile.js:
'use strict';
var path = require('path'),
gulp = require('gulp'),
del = require('del'),
typescript = require('gulp-typescript'),
sourcemaps = require('gulp-sourcemaps'),
browserify = require('browserify'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
uglify = require('gulp-uglify'),
gutil = require('gulp-util'),
inject = require('gulp-inject'),
babel = require('gulp-babel'),
argv = require('yargs').argv;
var devEnvironment = 'dev',
prodEnvironment = 'prod',
environment = argv.env || prodEnvironment,
isDevelopment = environment === devEnvironment;
var projectPath = __dirname,
srcDir = 'src',
srcPath = path.join(projectPath, srcDir),
buildDir = path.join('build', environment),
buildPath = path.join(projectPath, buildDir),
distDir = 'dist',
distRelativePath = path.join(buildDir, distDir),
distPath = path.join(buildPath, distDir);
var tsSrcPath = path.join(srcPath, 'typescript'),
tsGlob = path.join(tsSrcPath, '**', '*.ts'),
tsBuildPath = path.join(buildPath, 'tsc');
var indexHtmlName = 'index.html',
indexJsName = 'index.js';
var distIndexJsPath = path.join(distPath, 'index.js'),
distIndexHtmlPath = path.join(distPath, indexHtmlName);
var tsProject = typescript.createProject('tsconfig.json');
console.log('Environment: ' + environment);
gulp.task('clean', function () {
return del([buildPath]);
});
gulp.task('tsc', ['clean'], function () {
var stream = gulp.src([tsGlob]);
if (isDevelopment) {
stream = stream
.pipe(sourcemaps.init());
}
stream = stream
.pipe(typescript(tsProject))
.pipe(babel({
presets: ['es2015']
}));
if (isDevelopment) {
stream = stream.pipe(sourcemaps.write({sourceRoot: tsSrcPath}));
}
return stream.pipe(gulp.dest(tsBuildPath));
});
gulp.task('bundle', ['tsc'], function () {
var b = browserify({
entries: path.join(tsBuildPath, indexJsName),
debug: isDevelopment
});
var stream = b.bundle()
.pipe(source(indexJsName))
.pipe(buffer());
if (!isDevelopment) {
stream = stream.pipe(uglify());
}
return stream
.on('error', gutil.log)
.pipe(gulp.dest(distPath));
});
gulp.task('build', ['bundle'], function() {
return gulp.src(path.join(srcPath, indexHtmlName))
.pipe(inject(gulp.src([distIndexJsPath], {read: false}), {ignorePath: distRelativePath, addRootSlash: true}))
.pipe(gulp.dest(distPath));
});
You should pay attention to lines:
stream = stream.pipe(sourcemaps.write('', {sourceRoot: tsSrcPath})); - write inline source maps with sourceRoot pointing to your typescript sources path. Inline maps are written directly to .js files generated by tsc to build/dev/tsc.
debug: isDevelopment - in development environment make browserify generate his own source maps for resulting bundle build/dev/dist/index.js file so it will have source maps referencing .js files from build/dev/tsc which in turn have source maps referencing .ts files from src/typescript.
With this setup you will be able to see and debug .ts files in browser:
I faced similar issue when trying to debug my Angular2 app running in Chrome in Visual Studio Code (Using Debugger for Chrome extension)
I use gulp as my task runner and my setup is as follows:
Typescript files -> tsc -> intermediate es5 js -> browserify (plus uglify in production build) -> compiled bundle
My directory structure is as follows:
|- src
|- my .ts files here
|- main.ts - my entry file
|- dist
|- intermediate files go here
|- web
|- app.js - final bundle
|- app.js.map - final bundle map
|- gulpfile.js
gulpfile.js:
var gulp = require('gulp'),
tsc = require('gulp-typescript'),
browserify = require('browserify'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer');
gulp.task('tsc', [], () => {
return gulp.src(['src/**/*.ts'])
.pipe(sourcemaps.init())
.pipe(tsc({
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}))
.pipe(sourcemaps.write(null, {
"sourceRoot": function(file) {
let parts = file.relative.split('\\');
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
}))
.pipe(gulp.dest('dist/'));
});
gulp.task('bundle', ['tsc'], () => {
let b = browserify({
entries: 'dist/main.js',
debug: true,
});
return b.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./', {
"sourceRoot": "../",
}))
.pipe(gulp.dest('web/'));
})
gulp.task('default', ['bundle']);
Explanation/reasoning:
For some reason browserify doesn't read and parse .js.map files linked in .js file (via special comment at the end) but it does when the source map is embedded in js file. So, by passing null instead of path to sourcemaps it will embed it at the end of generated .js file.
Next issue I noticed was that sourcemaps doesn't automatically follow directory structure (add '../' to sourceRoot when it goes to next directory level), so I made a quick function to complement this. Keep in mind that it only works on Windows - on Linux you'd have to change split character.
function(file) {
let parts = file.relative.split('\\'); // put '/' here on Linux
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
Certainly there is a way to detect correct path separator, I'm debugging only on Windows thus it's not important for my purposes.
I hope it helps someone, cause I've spent whole Sunday morning tracking down this problem.