Skip to content

Commit

Permalink
fix(files): Ignore included:false pattern
Browse files Browse the repository at this point in the history
Define specificity of patterns.
Let most specific pattern decide to include files or not.

Closes #1530
  • Loading branch information
budde377 committed Mar 17, 2016
1 parent 8ef475f commit db42a7f
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 28 deletions.
11 changes: 11 additions & 0 deletions docs/config/02-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ Each pattern is either a simple string or an object with four properties:
* **Description.** Should the files be included in the browser using
`<script>` tag? Use `false` if you want to load them manually, eg.
using [Require.js](../plus/requirejs.html).

If a file is covered by multiple patterns with different `include` properties, the most specific pattern takes
precedence over the other.

The specificity of the pattern is defined as a six-tuple, where larger tuple implies lesser specificity:
*(n<sub>glob parts</sub>, n<sub>glob star</sub>, n<sub>star</sub>, n<sub>ext glob</sub>, n<sub>range</sub>, n<sub>optional</sub>)*.
Tuples are compared lexicographically.

The *n<sub>glob parts</sub>* is the number of patterns after the bracket sections are expanded. E.g. the
the pattern *{0...9}* will yield *n<sub>glob parts</sub>=10*. The rest of the tuple is decided as the least
specific of each expanded pattern.

### `served`
* **Type.** Boolean
Expand Down
5 changes: 5 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var Pattern = function (pattern, served, included, watched, nocache) {
this.included = helper.isDefined(included) ? included : true
this.watched = helper.isDefined(watched) ? watched : true
this.nocache = helper.isDefined(nocache) ? nocache : false
this.weight = helper.mmPatternWeight(pattern)
}

Pattern.prototype.compare = function (other) {
return helper.mmComparePatternWeights(this.weight, other.weight)
}

var UrlPattern = function (url) {
Expand Down
40 changes: 25 additions & 15 deletions lib/file-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,28 +214,38 @@ List.prototype._refresh = function () {
Object.defineProperty(List.prototype, 'files', {
get: function () {
var self = this
var uniqueFlat = function (list) {
return _.uniq(_.flatten(list), 'path')
}

var served = this._patterns.filter(function (pattern) {
return pattern.served
})
.map(function (p) {
var expandPattern = function (p) {
return from(self.buckets.get(p.pattern) || []).sort(byPath)
})
}

var included = this._patterns.filter(function (pattern) {
return pattern.included
var served = this._patterns.filter(function (pattern) {
return pattern.served
})
.map(function (p) {
return from(self.buckets.get(p.pattern) || []).sort(byPath)
.map(expandPattern)

var lookup = {}
var included = {}
this._patterns.forEach(function (p) {
var bucket = expandPattern(p)
bucket.forEach(function (file) {
var other = lookup[file.path]
if (other && other.compare(p) < 0) return
lookup[file.path] = p
if (p.included) {
included[file.path] = file
} else {
delete included[file.path]
}
})
})

var uniqFlat = function (list) {
return _.uniq(_.flatten(list), 'path')
}

return {
served: uniqFlat(served),
included: uniqFlat(included)
served: uniqueFlat(served),
included: _.values(included)
}
}
})
Expand Down
62 changes: 62 additions & 0 deletions lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var path = require('path')
var _ = require('lodash')
var useragent = require('useragent')
var Promise = require('bluebird')
var mm = require('minimatch')

exports.browserFullNameToShort = function (fullName) {
var agent = useragent.parse(fullName)
Expand All @@ -13,6 +14,67 @@ exports.browserFullNameToShort = function (fullName) {
exports.isDefined = function (value) {
return !_.isUndefined(value)
}
var parser = function (pattern, out) {
if (pattern.length === 0) return out
var p = /^(\[[^\]]*\]|[\*\+@\?]\((.+?)\))/g
var matches = p.exec(pattern)
if (!matches) {
var c = pattern[0]
var t = 'word'
if (c === '*') {
t = 'star'
} else if (c === '?') {
t = 'optional'
}
out[t]++
return parser(pattern.substring(1), out)
}
if (matches[2] !== undefined) {
out.ext_glob++
parser(matches[2], out)
return parser(pattern.substring(matches[0].length), out)
}
out.range++
return parser(pattern.substring(matches[0].length), out)
}

var gs_parser = function (pattern, out) {
if (pattern === '**') {
out.glob_star++
return out
}
return parser(pattern, out)
}

var compare_weight_object = function (w1, w2) {
return exports.mmComparePatternWeights(
[w1.glob_star, w1.star, w1.ext_glob, w1.range, w1.optional],
[w2.glob_star, w2.star, w2.ext_glob, w2.range, w2.optional]
)
}

exports.mmPatternWeight = function (pattern) {
var m = new mm.Minimatch(pattern)
if (!m.globParts) return [0, 0, 0, 0, 0, 0]
var result = m.globParts.reduce(function (prev, p) {
var r = p.reduce(function (prev, p) {
return gs_parser(p, prev)
}, {glob_star: 0, ext_glob: 0, word: 0, star: 0, optional: 0, range: 0})
if (prev === undefined) return r
return compare_weight_object(r, prev) > 0 ? r : prev
}, undefined)
result.glob_sets = m.set.length
return [result.glob_sets, result.glob_star, result.star, result.ext_glob, result.range, result.optional]
}

exports.mmComparePatternWeights = function (weight1, weight2) {
var n1, n2, diff
n1 = weight1[0]
n2 = weight2[0]
diff = n1 - n2
if (diff !== 0) return diff / Math.abs(diff)
return weight1.length > 1 ? exports.mmComparePatternWeights(weight1.slice(1), weight2.slice(1)) : 0
}

exports.isFunction = _.isFunction
exports.isString = _.isString
Expand Down
153 changes: 153 additions & 0 deletions test/e2e/files.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
Feature: Including files
In order to use Karma
As a person who wants to write great tests
I want to be able to include and exclude files

Scenario: Execute a test excluding a file
Given a configuration with:
"""
files = [
{pattern: 'files/log_foo.js', included: false},
'files/*.js'
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

Scenario: Execute a test excluding an explicitly included file
Given a configuration with:
"""
files = [
{pattern: 'files/log_foo.js', included: true},
{pattern: 'files/log_foo.js', included: false},
'files/*.js'
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

Scenario: Execute a test excluding an explicitly included file in another order
Given a configuration with:
"""
files = [
'files/*.js',
{pattern: 'files/log_foo.js', included: true},
{pattern: 'files/log_foo.js', included: false}
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

Scenario: Execute a test excluding an file included with brackets patterns
Given a configuration with:
"""
files = [
'files/test.js',
{pattern: 'files/log_foo.js', included: false},
{pattern: 'files/{log,bug}_foo.js', included: true}
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

Scenario: Execute a test excluding an file included with wildcard
Given a configuration with:
"""
files = [
'files/test.js',
{pattern: 'files/+(log|bug)_foo.js', included: false},
{pattern: 'files/*.js', included: true}
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

Scenario: Execute a test excluding an file included with glob-star
Given a configuration with:
"""
files = [
'files/test.js',
{pattern: 'files/*.js', included: false},
{pattern: 'files/**', included: true}
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""


Scenario: Execute a test excluding an file included with ext. glob patterns
Given a configuration with:
"""
files = [
'files/test.js',
{pattern: 'files/+(log|bug)_foo.js', included: false},
{pattern: 'files/{log,bug}_foo.js', included: true}
];
browsers = ['PhantomJS'];
plugins = [
'karma-jasmine',
'karma-phantomjs-launcher'
];
"""
When I start Karma
Then it passes with:
"""
.
PhantomJS
"""

2 changes: 2 additions & 0 deletions test/e2e/support/files/log_foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log('foo')
5 changes: 5 additions & 0 deletions test/e2e/support/files/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('plus', function () {
it('should pass', function () {
expect(true).toBe(true)
})
})
Loading

0 comments on commit db42a7f

Please sign in to comment.