-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[WIP] Add listener/watcher/observer assertions, many bug fixes #12892
Changes from all commits
15e4fa0
f0608dc
ff235f3
63d38c6
2cc7dbc
7552f6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import { assert, runInDebug } from 'ember-metal/debug'; | ||
|
||
/* | ||
When we render a rich template hierarchy, the set of events that | ||
*might* happen tends to be much larger than the set of events that | ||
|
@@ -20,6 +22,24 @@ export var protoMethods = { | |
if (!this._listeners) { | ||
this._listeners = []; | ||
} | ||
|
||
runInDebug(() => { | ||
// Assert that an observer is only used once. | ||
let listeners = this._listeners; | ||
let matchFound = false; | ||
|
||
for (let i = listeners.length - 4; i >= 0; i -= 4) { | ||
if (listeners[i+1] === target && | ||
listeners[i+2] === method && | ||
listeners[i] === eventName) { | ||
matchFound = true; | ||
break; | ||
} | ||
} | ||
|
||
assert(`Tried to add a duplicate listener for ${eventName}`, !matchFound); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be nice to have the object's toString here in the message, to make it easier to track down the source. Would that work? |
||
}); | ||
|
||
this._listeners.push(eventName, target, method, flags); | ||
}, | ||
|
||
|
@@ -39,6 +59,7 @@ export var protoMethods = { | |
}, | ||
|
||
removeFromListeners(eventName, target, method, didRemove) { | ||
let matchFound = false; | ||
let pointer = this; | ||
while (pointer) { | ||
let listeners = pointer._listeners; | ||
|
@@ -51,19 +72,23 @@ export var protoMethods = { | |
didRemove(eventName, target, listeners[index + 2]); | ||
} | ||
listeners.splice(index, 4); | ||
matchFound = true; | ||
} else { | ||
// we are trying to remove an inherited listener, so we do | ||
// just-in-time copying to detach our own listeners from | ||
// our inheritance chain. | ||
this._finalizeListeners(); | ||
return this.removeFromListeners(eventName, target, method); | ||
this.removeFromListeners(eventName, target, method); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
if (pointer._listenersFinalized) { break; } | ||
pointer = pointer.parent; | ||
} | ||
|
||
assert(`Tried to remove a non-existant listener for ${eventName}`, matchFound); | ||
}, | ||
|
||
matchingListeners(eventName) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
import isEnabled from 'ember-metal/features'; | ||
import { | ||
meta as metaFor | ||
} from 'ember-metal/meta'; | ||
import { assert } from 'ember-metal/debug'; | ||
import { meta as metaFor } from 'ember-metal/meta'; | ||
import { | ||
MANDATORY_SETTER_FUNCTION, | ||
DEFAULT_GETTER_FUNCTION, | ||
|
@@ -79,8 +78,14 @@ if (isEnabled('mandatory-setter')) { | |
} | ||
|
||
export function unwatchKey(obj, keyName, meta) { | ||
// can't unwatch length on Array - it is special... | ||
if (keyName === 'length' && Array.isArray(obj)) { return; } | ||
|
||
var m = meta || metaFor(obj); | ||
let count = m.peekWatching(keyName); | ||
|
||
assert(`Tried to unwatch '${keyName}' on object but no one was watching`, count > 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question as above, can we include obj.toString() in the message? |
||
|
||
if (count === 1) { | ||
m.writeWatching(keyName, 0); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,7 +90,7 @@ testBoth('Connecting a binding to path', function(get, set) { | |
equal(get(a, 'foo'), 'BIFF', 'a should have changed'); | ||
}); | ||
|
||
testBoth('Calling connect more than once', function(get, set) { | ||
testBoth('Calling connect more than once throws an error', function(get, set) { | ||
var b = { bar: 'BAR' }; | ||
var a = { foo: 'FOO', b: b }; | ||
|
||
|
@@ -100,7 +100,9 @@ testBoth('Calling connect more than once', function(get, set) { | |
performTest(binding, a, b, get, set, function () { | ||
binding.connect(a); | ||
|
||
binding.connect(a); | ||
throws(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an error or an assertion? If an assertion, you will need to use expectAssertion to allow the production builds to pass. |
||
binding.connect(a); | ||
}, /Tried to add.*b.bar/); | ||
}); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,15 +56,15 @@ QUnit.test('listeners should be inherited', function() { | |
}); | ||
|
||
|
||
QUnit.test('adding a listener more than once should only invoke once', function() { | ||
QUnit.test('adding a listener more than once throws an error', function() { | ||
var obj = {}; | ||
var count = 0; | ||
var F = function() { count++; }; | ||
addListener(obj, 'event!', F); | ||
addListener(obj, 'event!', F); | ||
|
||
sendEvent(obj, 'event!'); | ||
equal(count, 1, 'should only invoke once'); | ||
throws(function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, I believe this needs to be expectAssertion |
||
addListener(obj, 'event!', F); | ||
}, /Tried to add a duplicate listener for event!/); | ||
}); | ||
|
||
QUnit.test('adding a listener with a target should invoke with target', function() { | ||
|
@@ -196,7 +196,9 @@ QUnit.test('while suspended, it should not be possible to add a duplicate listen | |
addListener(obj, 'event!', target, target.method); | ||
|
||
function callback() { | ||
addListener(obj, 'event!', target, target.method); | ||
throws(function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likely should be expectAssertion |
||
addListener(obj, 'event!', target, target.method); | ||
}, /Tried to add a duplicate listener for event!/); | ||
} | ||
|
||
sendEvent(obj, 'event!'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,12 +66,19 @@ QUnit.test('meta.listeners inheritance', function(assert) { | |
assert.equal(matching.length, 3); | ||
}); | ||
|
||
QUnit.test('meta.listeners deduplication', function(assert) { | ||
QUnit.test('meta.listeners adding an existing listener throws an error', function(assert) { | ||
let t = {}; | ||
let m = meta({}); | ||
m.addToListeners('hello', t, 'm', 0); | ||
m.addToListeners('hello', t, 'm', 0); | ||
let matching = m.matchingListeners('hello'); | ||
assert.equal(matching.length, 3); | ||
assert.equal(matching[0], t); | ||
throws(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expect assertion |
||
m.addToListeners('hello', t, 'm', 0); | ||
}, /Tried to add a duplicate listener for hello/); | ||
}); | ||
|
||
QUnit.test('meta.listeners removing a non-existing listener throws an error', function(assert) { | ||
let t = {}; | ||
let m = meta({}); | ||
throws(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expectAssertion |
||
m.removeFromListeners('hello', t, 'm'); | ||
}, /Tried to remove a non-existant listener for hello/); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1166,3 +1166,29 @@ testBoth('observer switched on and off and then setter', function (get, set) { | |
|
||
deepEqual(Object.keys(beer), ['type']); | ||
}); | ||
|
||
testBoth('adding the same observer multiple times throws an error', function (get, set) { | ||
function Beer() { } | ||
Beer.prototype.type = 'ipa'; | ||
|
||
let beer = new Beer(); | ||
|
||
addObserver(beer, 'type', K); | ||
|
||
throws(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏈 |
||
addObserver(beer, 'type', K); | ||
}, /Tried to add.*type/); | ||
}); | ||
|
||
testBoth('removing a non-existant observer throws an error', function (get, set) { | ||
function Beer() { } | ||
Beer.prototype.type = 'ipa'; | ||
|
||
let beer = new Beer(); | ||
|
||
addObserver(beer, 'type', function() {}); | ||
|
||
throws(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚾ |
||
removeObserver(beer, 'type', K); | ||
}, /Tried to remove.*type/); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSCS rules are failing for the lack of space between 'i' and '+' (lines below also).