Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): Add configuration for javascript version. #1939

2 changes: 1 addition & 1 deletion client/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var StatusUpdater = function (socket, titleElement, bannerElement, browsersEleme

socket.on('connect', updateBanner('connected'))
socket.on('disconnect', updateBanner('disconnected'))
socket.on('reconnecting', updateBanner('reconnecting in $ ms...'))
socket.on('reconnecting', updateBanner('reconnecting in $ seconds...'))
socket.on('reconnect', updateBanner('connected'))
socket.on('reconnect_failed', updateBanner('failed to reconnect'))
socket.on('info', updateBrowsersInfo)
Expand Down
47 changes: 47 additions & 0 deletions docs/config/04-preprocessors.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,50 @@ return `false` and the preprocessor would not be executed on the CoffeeScript fi
[custom plugins]: ../dev/plugins.html
[plugins]: plugins.html
[issue788]: https://github.com/karma-runner/karma/issues/788

## Order of execution

If a file matches only one key in the preprocessors config object, then karma
will execute the preprocessors over that file in the order they are listed in
the corresponding array. So for instance if the config object is:

```js
preprocessors: {
'*.js': ['a', 'b']
}
```

Then karma will execute `'a'` before executing `'b'`.

If a file matches multiple keys, karma will do its best to execute the
preprocessors in a reasonable order. So if you have:

```js
preprocessors: {
'*.js': ['a', 'b'],
'a.*': ['b', 'c']
}
```

then for `a.js`, karma will run `'a'` then `'b'` then `'c'`. If two lists
contradict eachother, like:

```js
preprocessors: {
'*.js': ['a', 'b'],
'a.*': ['b', 'a']
}
```

then karma will arbitrarily pick one list to prioritize over the other. In a
case like:

```js
preprocessors: {
'*.js': ['a', 'b', 'c'],
'a.*': ['c', 'b', 'd']
}
```

Then `'a'` will definitely be run first, `'d'` will definitely be run last, but
it's arbitrarily if karma will run `'b'` before `'c'` or vice versa.
2 changes: 1 addition & 1 deletion docs/intro/01-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Karma runs on [Node.js] and is available as an [NPM] package.
## Installing Node.js

On Mac or Linux we recommend using [NVM](https://github.com/creationix/nvm). On Windows, download Node.js
from [the official site](https://nodejs.org/).
from [the official site](https://nodejs.org/) or use the [NVM PowerShell Module](https://www.powershellgallery.com/packages/nvm).

Note: Karma currently works on Node.js **0.10**, **0.12.x**, **4.x**, and **5.x**. See [FAQ] for more info.

Expand Down
30 changes: 27 additions & 3 deletions lib/middleware/karma.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
var path = require('path')
var util = require('util')
var url = require('url')
var useragent = require('useragent')

var urlparse = function (urlStr) {
var urlObj = url.parse(urlStr, true)
Expand Down Expand Up @@ -59,9 +60,21 @@ var getXUACompatibleUrl = function (url) {
return value
}

var isFirefox = function (req) {
if (!(req && req.headers)) {
return false
}

// Browser check
var firefox = useragent.is(req.headers['user-agent']).firefox

return firefox
}

var createKarmaMiddleware = function (filesPromise, serveStaticFile, serveFile,
/* config.basePath */ basePath, /* config.urlRoot */ urlRoot, /* config.client */ client,
/* config.customContextFile */ customContextFile, /* config.customDebugFile */ customDebugFile) {
/* config.customContextFile */ customContextFile, /* config.customDebugFile */ customDebugFile,
/* config.jsVersion */ jsVersion) {
return function (request, response, next) {
var requestUrl = request.normalizedUrl.replace(/\?.*/, '')

Expand Down Expand Up @@ -144,13 +157,24 @@ var createKarmaMiddleware = function (filesPromise, serveStaticFile, serveFile,
return util.format(LINK_TAG_HTML, filePath)
}

return util.format(SCRIPT_TAG, SCRIPT_TYPE[fileExt] || 'text/javascript', filePath)
// The script tag to be placed
var scriptType = (SCRIPT_TYPE[fileExt] || 'text/javascript')

// In case there is a JavaScript version specified and this is a Firefox browser
if (jsVersion && isFirefox(request)) {
scriptType += ';version=' + jsVersion
}

return util.format(SCRIPT_TAG, scriptType, filePath)
})

// TODO(vojta): don't compute if it's not in the template
var mappings = files.served.map(function (file) {
// Windows paths contain backslashes and generate bad IDs if not escaped
var filePath = filePathToUrlPath(file.path, basePath, urlRoot).replace(/\\/g, '\\\\')
// Escape single quotes that might be in the filename -
// double quotes should not be allowed!
filePath = filePath.replace(/'/g, '\\\'')

return util.format(" '%s': '%s'", filePath, file.sha)
})
Expand Down Expand Up @@ -196,7 +220,7 @@ var createKarmaMiddleware = function (filesPromise, serveStaticFile, serveFile,

createKarmaMiddleware.$inject = ['filesPromise', 'serveStaticFile', 'serveFile',
'config.basePath', 'config.urlRoot', 'config.client', 'config.customContextFile',
'config.customDebugFile']
'config.customDebugFile', 'config.jsVersion']

// PUBLIC API
exports.create = createKarmaMiddleware
47 changes: 26 additions & 21 deletions lib/preprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var fs = require('graceful-fs')
var crypto = require('crypto')
var mm = require('minimatch')
var isBinaryFile = require('isbinaryfile')
var combineLists = require('combine-lists')

var log = require('./logger').create('preprocess')

Expand Down Expand Up @@ -64,9 +65,11 @@ var createPreprocessor = function (config, basePath, injector) {
return p
}

var allPreprocessors = []
patterns.forEach(function (pattern) {
config[pattern].forEach(instantiatePreprocessor)
allPreprocessors = combineLists(allPreprocessors, config[pattern])
})
allPreprocessors.forEach(instantiatePreprocessor)

return function preprocess (file, done) {
patterns = Object.keys(config)
Expand All @@ -81,36 +84,38 @@ var createPreprocessor = function (config, basePath, injector) {
throw err
}

var preprocessors = []
var nextPreprocessor = createNextProcessor(preprocessors, file, done)

var preprocessorNames = []
for (var i = 0; i < patterns.length; i++) {
if (mm(file.originalPath, patterns[i], {dot: true})) {
if (thisFileIsBinary) {
log.warn('Ignoring preprocessing (%s) %s because it is a binary file.',
config[patterns[i]].join(', '), file.originalPath)
} else {
config[patterns[i]].forEach(function (name) {
var p = instances[name]
if (p == null) {
p = instantiatePreprocessor(name)
}

if (p == null) {
if (!alreadyDisplayedWarnings[name]) {
alreadyDisplayedWarnings[name] = true
log.warn('Failed to instantiate preprocessor %s', name)
}
return
}

instances[name] = p
preprocessors.push(p)
})
preprocessorNames = combineLists(preprocessorNames, config[patterns[i]])
}
}
}

var preprocessors = []
var nextPreprocessor = createNextProcessor(preprocessors, file, done)
preprocessorNames.forEach(function (name) {
var p = instances[name]
if (p == null) {
p = instantiatePreprocessor(name)
}

if (p == null) {
if (!alreadyDisplayedWarnings[name]) {
alreadyDisplayedWarnings[name] = true
log.warn('Failed to instantiate preprocessor %s', name)
}
return
}

instances[name] = p
preprocessors.push(p)
})

nextPreprocessor(null, thisFileIsBinary ? buffer : buffer.toString())
})
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
"body-parser": "^1.12.4",
"chokidar": "^1.4.1",
"colors": "^1.1.0",
"combine-lists": "^1.0.0",
"connect": "^3.3.5",
"core-js": "^2.1.0",
"di": "^0.0.1",
Expand Down
4 changes: 4 additions & 0 deletions test/client/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if (process.env.TRAVIS) {
} else {
browsers.push('Chrome')
}
browsers.push('Firefox')

module.exports = function (config) {
config.set({
Expand Down Expand Up @@ -156,6 +157,9 @@ module.exports = function (config) {

forceJSONP: true,

// Specify JavaScript version for Firefox browser (for now)
jsVersion: 1.8,

browserStack: {
project: 'Karma'
}
Expand Down
14 changes: 14 additions & 0 deletions test/e2e/support/tag/tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable no-unused-vars */
var isFirefox = function () {
return typeof InstallTrigger !== 'undefined'
}

var containsJsTag = function () {
var scripts = document.getElementsByTagName('script')
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].type.indexOf(';version=') > -1) {
return true
}
}
return false
}
6 changes: 6 additions & 0 deletions test/e2e/support/tag/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* globals containsJsTag, isFirefox */
describe('JavaScript version tag', function () {
it('should add the version tag, if Firefox is used', function () {
expect(containsJsTag()).toBe(isFirefox())
})
})
41 changes: 41 additions & 0 deletions test/e2e/tag.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Feature: JavaScript Tag
In order to use Karma
As a person who wants to write great tests
I want to be able to run tests from the command line.

Scenario: Execute a test in Firefox with version, with JavaScript tag
Given a configuration with:
"""
files = ['tag/tag.js', 'tag/test.js'];
browsers = ['Firefox']
jsVersion = 1.8
plugins = [
'karma-jasmine',
'karma-firefox-launcher'
]
"""
When I start Karma
Then it passes with:
"""
.
Firefox
"""

Scenario: Execute a test in Chrome with version, without JavaScript tag
Given a configuration with:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will not actually check that the version tag is available, you should add another file that actually checks the html for the version tag

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would I be able to check the HTML? I am not familiar with e2e tests.

I probably need to change the core_steps.js file to add a contains or something. However, could you help with the way to get that dynamic HTML? The HTML is changed by means of JavaScript, so how would I do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create a new file similar to what is in basic/test.js, e.g. basic/version.js and include it in the files here. That means that file will be run in the browser. Now in that file you can put some js in that reads the current html and checks for the version tag to be present, and fails if it's not present.

"""
files = ['tag/tag.js', 'tag/test.js'];
browsers = ['Chrome'];
jsVersion = 1.8;
plugins = [
'karma-jasmine',
'karma-chrome-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
Chrome
"""

15 changes: 15 additions & 0 deletions test/unit/middleware/karma.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,21 @@ describe('middleware.karma', () => {
callHandlerWith('/__karma__/context.html')
})

it('should escape quotes in mappings with all served files', (done) => {
fsMock._touchFile('/karma/static/context.html', 0, '%MAPPINGS%')
servedFiles([
new MockFile("/some/abc/a'b.js", 'sha_a'),
new MockFile('/base/path/ba.js', 'sha_b')
])

response.once('end', () => {
expect(response).to.beServedAs(200, 'window.__karma__.files = {\n \'/__karma__/absolute/some/abc/a\\\'b.js\': \'sha_a\',\n \'/__karma__/base/ba.js\': \'sha_b\'\n};\n')
done()
})

callHandlerWith('/__karma__/context.html')
})

it('should serve debug.html with replaced script tags without timestamps', (done) => {
includedFiles([
new MockFile('/first.js'),
Expand Down
50 changes: 50 additions & 0 deletions test/unit/preprocessor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,54 @@ describe('preprocessor', () => {
done()
})
})

it('should merge lists of preprocessors', (done) => {
var callOrder = []
var fakePreprocessorA = sinon.spy((content, file, done) => {
callOrder.push('a')
done(null, content)
})
var fakePreprocessorB = sinon.spy((content, file, done) => {
callOrder.push('b')
done(null, content)
})
var fakePreprocessorC = sinon.spy((content, file, done) => {
callOrder.push('c')
done(null, content)
})
var fakePreprocessorD = sinon.spy((content, file, done) => {
callOrder.push('d')
done(null, content)
})

var injector = new di.Injector([{
'preprocessor:fakeA': ['factory', () => fakePreprocessorA],
'preprocessor:fakeB': ['factory', () => fakePreprocessorB],
'preprocessor:fakeC': ['factory', () => fakePreprocessorC],
'preprocessor:fakeD': ['factory', () => fakePreprocessorD]
}])

pp = m.createPreprocessor({
'/*/a.js': ['fakeA', 'fakeB'],
'/some/*': ['fakeB', 'fakeC'],
'/some/a.js': ['fakeD']
}, null, injector)

var file = {originalPath: '/some/a.js', path: 'path'}

pp(file, (err) => {
if (err) throw err

expect(fakePreprocessorA).to.have.been.called
expect(fakePreprocessorB).to.have.been.called
expect(fakePreprocessorC).to.have.been.called
expect(fakePreprocessorD).to.have.been.called

expect(callOrder.indexOf('d')).not.to.equal(-1)
expect(callOrder.filter((letter) => {
return letter !== 'd'
})).to.eql(['a', 'b', 'c'])
done()
})
})
})