Skip to content

Commit 46c6033

Browse files
committed
WIP: implement code coverage with karma-coverage
TODO: - [ ] implement karma running with grunt so we can have multiple karma configs (need specific settings for coverage) - [ ] 100% test coverage or ignore utils file - [ ] setup coveralls with grunt-karma-coveralls - [ ] add coveralls badge to README
1 parent bb47f7c commit 46c6033

14 files changed

+401
-95
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules/
2+
coverage/

karma.conf.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,46 @@ module.exports = function(config) {
1414

1515
// list of files / patterns to load in the browser
1616
files: [
17-
'tests/*-tests.js'
17+
'tests/*-tests.js',
1818
],
1919

2020

2121
// list of files to exclude
2222
exclude: [
2323
],
2424

25+
reporters: ['html'],
2526

2627
// preprocess matching files before serving them to the browser
2728
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
2829
preprocessors: {
29-
'tests/*-tests.js': ['webpack']
30+
'tests/*-tests.js': ['webpack'],
31+
},
32+
33+
coverageReporter: {
34+
type: "html",
35+
dir: "coverage/"
3036
},
3137

3238
webpack: {
3339
module: {
3440
loaders: [
3541
{ test: /\.js$/, loader: 'jstransform-loader' },
42+
],
43+
postLoaders: [
44+
{
45+
test: /\.js$/,
46+
exclude: /(test|node_modules|bower_components)\//,
47+
loader: 'istanbul-instrumenter'
48+
}
3649
]
3750
},
51+
3852
},
3953

4054
// test results reporter to use
4155
// possible values: 'dots', 'progress'
4256
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
43-
reporters: ['html'],
4457

4558

4659
// web server port

package.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"description": "Immutable, reactive Flux architecture. UI Agnostic.",
55
"main": "dist/nuclear.js",
66
"scripts": {
7-
"test": "./node_modules/karma/bin/karma start --single-run --browsers=PhantomJS --reporters=progress --log-level error",
8-
"test-html": "karma start"
7+
"test": "./node_modules/karma/bin/karma start --single-run --browsers=PhantomJS --reporters=progress --log-level=error",
8+
"coverage": "./node_modules/karma/bin/karma start --single-run --browsers=PhantomJS --reporters=coverage --log-level=error",
9+
"test-html": "./node_modules/karma/bin/karma start --browsers=Chrome --reporters=html --log-level=error"
910
},
1011
"author": "Jordan Garcia",
1112
"license": "MIT",
@@ -44,6 +45,11 @@
4445
"react": "^0.13.2",
4546
"karma-phantomjs-launcher": "^0.1.4",
4647
"karma-es5-shim": "https://github.com/pokehanai/karma-es5-shim/archive/v2.1.0.tar.gz",
47-
"webpack": "^1.8.11"
48+
"webpack": "^1.8.11",
49+
"grunt": "^0.4.5",
50+
"karma-coverage": "^0.3.1",
51+
"grunt-karma-coveralls": "^2.5.3",
52+
"istanbul": "^0.3.13",
53+
"istanbul-instrumenter-loader": "^0.1.2"
4854
}
4955
}

src/change-observer.js

+34-21
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,41 @@ class ChangeObserver {
1616
constructor(initialState, evaluator) {
1717
this.__prevState = initialState
1818
this.__evaluator = evaluator
19-
this.__prevValues = Immutable.Map({})
19+
this.__prevValues = Immutable.Map()
2020
this.__observers = []
2121
}
2222

2323
/**
2424
* @param {Immutable.Map} newState
2525
*/
2626
notifyObservers(newState) {
27-
var currentValues = Immutable.Map()
27+
if (this.__observers.length > 0) {
28+
var currentValues = Immutable.Map()
2829

29-
this.__observers.forEach(entry => {
30-
var getter = entry.getter
31-
var code = hashCode(getter)
32-
var prevState = this.__prevState
33-
var prevValue
30+
this.__observers.forEach(entry => {
31+
var getter = entry.getter
32+
var code = hashCode(getter)
33+
var prevState = this.__prevState
34+
var prevValue
3435

35-
if (this.__prevValues.has(code)) {
36-
prevValue = this.__prevValues.get(code)
37-
} else {
38-
prevValue = this.__evaluator.evaluate(prevState, getter)
39-
this.__prevValues = this.__prevValues.set(code, prevValue)
40-
}
36+
if (this.__prevValues.has(code)) {
37+
prevValue = this.__prevValues.get(code)
38+
} else {
39+
prevValue = this.__evaluator.evaluate(prevState, getter)
40+
this.__prevValues = this.__prevValues.set(code, prevValue)
41+
}
4142

42-
var currValue = this.__evaluator.evaluate(newState, getter)
43+
var currValue = this.__evaluator.evaluate(newState, getter)
4344

44-
if (!isEqual(prevValue, currValue)) {
45-
entry.handler.call(null, currValue)
46-
currentValues = currentValues.set(code, currValue)
47-
}
48-
})
45+
if (!isEqual(prevValue, currValue)) {
46+
entry.handler.call(null, currValue)
47+
currentValues = currentValues.set(code, currValue)
48+
}
49+
})
50+
51+
this.__prevValues = currentValues
52+
}
4953
this.__prevState = newState
50-
this.__prevValues = currentValues
5154
}
5255

5356
/**
@@ -74,6 +77,16 @@ class ChangeObserver {
7477
}
7578
}
7679

80+
/**
81+
* Updates the change appliers prevState when silent store registration happens
82+
* this allows the proper hasChanged check for notify observers if a store registration
83+
* happens in between
84+
* @param {Immutable.Map} prevState
85+
*/
86+
updatePreviousState(prevState) {
87+
this.__prevState = prevState
88+
}
89+
7790
/**
7891
* Resets and clears all observers and reinitializes back to the supplied
7992
* previous state
@@ -82,7 +95,7 @@ class ChangeObserver {
8295
*/
8396
reset(prevState) {
8497
this.__prevState = prevState
85-
this.__prevValues = Immutable.Map({})
98+
this.__prevValues = Immutable.Map()
8699
this.__observers = []
87100
}
88101
}

src/evaluator.js

-2
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ class Evaluator {
134134
* @param {Getter}
135135
*/
136136
untrack(getter) {
137-
var code = hashCode(getter)
138-
this.__cachedGetters = this.__cachedGetters.remove(code)
139137
// TODO untrack all depedencies
140138
}
141139

src/getter.js

-21
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,6 @@ function isGetter(toTest) {
1919
return (isArray(toTest) && isFunction(toTest[toTest.length - 1]))
2020
}
2121

22-
23-
/**
24-
* Recursive function to flatten deps of a getter
25-
* @param {Getter} getter
26-
* @return {Array.<KeyPath>} unique flatten deps
27-
*/
28-
function unwrapDeps(getter) {
29-
var accum = Immutable.Set()
30-
var deps = getter.slice(0, getter.length - 1)
31-
32-
return accum.withMutations(accum => {
33-
deps.forEach((dep) => {
34-
isGetter(dep)
35-
? accum.union(unwrapDeps(dep))
36-
: accum.add(dep)
37-
})
38-
return accum
39-
})
40-
}
41-
4222
/**
4323
* Returns the compute function from a getter
4424
* @param {Getter} getter
@@ -71,7 +51,6 @@ function fromKeyPath(keyPath) {
7151

7252

7353
module.exports = {
74-
unwrapDeps: unwrapDeps,
7554
isGetter: isGetter,
7655
getComputeFn: getComputeFn,
7756
getDeps: getDeps,

src/hash-code.js

-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ module.exports = function(getter, dontCache) {
1616
return getter.__hashCode
1717
}
1818

19-
if (!isGetter(getter)) {
20-
throw new Error("Invalid getter! Must be of the form: [<KeyPath>, ...<KeyPath>, <function>]")
21-
}
22-
2319
var hashCode = Immutable.fromJS(getter).hashCode()
2420

2521
if (!dontCache) {

src/reactor.js

+14-29
Original file line numberDiff line numberDiff line change
@@ -135,41 +135,26 @@ class Reactor {
135135
}
136136

137137
/**
138-
* Attachs a store to a non-running or running nuclear reactor. Will emit change
139-
* @param {string} id
140-
* @param {Store} store
141-
* @param {boolean} silent should not notify observers of state change
138+
* @param {Array.<string, Store>} stores
142139
*/
143-
registerStore(id, store, silent) {
144-
if (this.__stores.get(id)) {
145-
console.warn("Store already defined for id=" + id)
146-
}
140+
registerStores(stores) {
141+
each(stores, (store, id) => {
142+
if (this.__stores.get(id)) {
143+
console.warn("Store already defined for id=" + id)
144+
}
147145

148-
var initialState = store.getInitialState()
146+
var initialState = store.getInitialState()
149147

150-
if (this.debug && !isImmutableValue(initialState)) {
151-
throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable")
152-
}
148+
if (this.debug && !isImmutableValue(initialState)) {
149+
throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable")
150+
}
153151

154-
this.__stores = this.__stores.set(id, store)
155-
this.__state = this.__state.set(id, initialState)
152+
this.__stores = this.__stores.set(id, store)
153+
this.__state = this.__state.set(id, initialState)
156154

157-
if (!silent) {
158-
this.__changeObserver.notifyObservers(this.__state)
159-
}
160-
}
161-
162-
/**
163-
* @param {Array.<string, Store>} stores
164-
* @param {boolean} silent should not notify observers of state change
165-
*/
166-
registerStores(stores, silent) {
167-
each(stores, (store, id) => {
168-
this.registerStore(id, store, true)
169155
})
170-
if (!silent) {
171-
this.__changeObserver.notifyObservers(this.__state)
172-
}
156+
157+
this.__changeObserver.notifyObservers(this.__state)
173158
}
174159

175160
/**

src/utils.js

-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
/**
2-
* Ensures that the inputted value is an array
3-
* @param {*} val
4-
* @return {array}
5-
*/
6-
exports.coerceArray = function(val) {
7-
if (!exports.isArray(val)) {
8-
return [val]
9-
}
10-
return val
11-
}
12-
131
/**
142
* Checks if the passed in value is a number
153
* @param {*} val

tests/evaluator-tests.js

+10
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ describe('Evaluator', () => {
138138
})
139139
})
140140

141+
describe("when evaluating an invalid keypath / getter", () => {
142+
it("should throw an error", () => {
143+
var invalidGetter = { foo: 'bar' };
144+
145+
expect(function() {
146+
evaluator.evaluate(invalidGetter)
147+
}).toThrow()
148+
})
149+
})
150+
141151
describe("when evaluating a getter that returns a mutable value", () => {
142152
it("should return a reference to that value", () => {
143153
var proj1 = { id: 1, description: 'proj 1' }

tests/getter-tests.js

+9
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,13 @@ describe('Getter', () => {
2323
expect(isGetter([getter1, ['foo'], (a, b) => a + b])).toBe(true)
2424
})
2525
})
26+
27+
describe("fromKeyPath", () => {
28+
it('should throw an Error for a nonvalid KeyPath', () => {
29+
var invalidKeypath = 'foo.bar'
30+
expect(function() {
31+
Getter.fromKeyPath(invalidKeypath)
32+
}).toThrow()
33+
})
34+
})
2635
})

0 commit comments

Comments
 (0)