-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31 from canjs/minor
can-simple-observable 2.1
- Loading branch information
Showing
4 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
var QUnit = require("steal-qunit"); | ||
var keyObservable = require("./key"); | ||
var canReflect = require("can-reflect"); | ||
var canReflectDeps = require("can-reflect-dependencies"); | ||
var SimpleMap = require("can-simple-map"); | ||
|
||
var onlyDevTest = steal.isEnv("production") ? QUnit.skip : QUnit.test; | ||
|
||
QUnit.module("can-simple-observable/key"); | ||
|
||
QUnit.test("basics", function(assert) { | ||
var outer = {inner: {key: "hello"}}; | ||
var observable = keyObservable(outer, "inner.key"); | ||
|
||
// Unbound and unobserved behavior | ||
assert.equal(canReflect.getValue(observable), "hello", "getValue unbound"); | ||
|
||
canReflect.setValue(observable, "aloha"); | ||
assert.equal(canReflect.getValue(outer).inner.key, "aloha", "value set"); | ||
assert.equal(canReflect.getValue(observable), "aloha", "getValue unbound"); | ||
}); | ||
|
||
QUnit.test("get and set Priority", function(assert) { | ||
var outer = {inner: {key: "hello"}}; | ||
var observable = keyObservable(outer, "inner.key"); | ||
|
||
canReflect.setPriority(observable, 5); | ||
assert.equal(canReflect.getPriority(observable), 5, "set priority"); | ||
}); | ||
|
||
onlyDevTest("observable has a helpful name", function() { | ||
var outer = {inner: {key: "hello"}}; | ||
var observable = keyObservable(outer, "inner.key"); | ||
QUnit.equal( | ||
canReflect.getName(observable), | ||
"keyObservable<Object{}.inner.key>", | ||
"observable has the correct name" | ||
); | ||
}); | ||
|
||
onlyDevTest("dependency data", function(assert) { | ||
var outer = new SimpleMap({inner: new SimpleMap({key: "hello"})}); | ||
var observable = keyObservable(outer, "inner.key"); | ||
|
||
// The observable must be bound before it returns dependency data | ||
canReflect.onValue(observable, function() {}); | ||
|
||
// Check the observable’s dependency data | ||
var observableDepData = canReflectDeps.getDependencyDataOf(observable); | ||
assert.deepEqual( | ||
observableDepData, | ||
{ | ||
whatChangesMe: { | ||
derive: { | ||
keyDependencies: new Map([ | ||
// the observable is changed by outer’s 'inner' property | ||
[outer, new Set(["inner"])], | ||
// the observable is changed by outer.inner’s 'key' property | ||
[outer.get("inner"), new Set(["key"])] | ||
]) | ||
} | ||
}, | ||
whatIChange: { | ||
mutate: { | ||
keyDependencies: new Map([ | ||
// the observable changes outer.inner’s 'key' property | ||
[outer.get("inner"), new Set(["key"])] | ||
]) | ||
} | ||
} | ||
}, | ||
"the observable has the correct mutation dependencies" | ||
); | ||
|
||
// Check outer.inner’s dependency data | ||
var innerDepData = canReflectDeps.getDependencyDataOf(outer, "inner"); | ||
assert.deepEqual( | ||
innerDepData, | ||
{ | ||
whatIChange: { | ||
derive: { | ||
// outer.inner changes the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
} | ||
}, | ||
"outer.inner has the correct mutation dependencies" | ||
); | ||
|
||
// Check outer.inner.key’s dependency data | ||
var keyDepData = canReflectDeps.getDependencyDataOf(outer.get("inner"), "key"); | ||
assert.deepEqual( | ||
keyDepData, | ||
{ | ||
whatChangesMe: { | ||
mutate: { | ||
// outer.inner.key has zero mutate.keyDependencies | ||
keyDependencies: new Map(), | ||
// outer.inner.key is changed by the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
}, | ||
whatIChange: { | ||
derive: { | ||
// outer.inner.key changes the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
} | ||
}, | ||
"outer.inner.key has the correct mutation dependencies" | ||
); | ||
|
||
}); | ||
|
||
QUnit.test("works when the keys change", function(assert) { | ||
var originalInner = new SimpleMap({key: "hello"}); | ||
var outer = new SimpleMap({inner: originalInner}); | ||
var observable = keyObservable(outer, "inner.key"); | ||
|
||
// Test initial value | ||
assert.equal(canReflect.getValue(observable), "hello", "initial value is correct"); | ||
|
||
// Change the value of a key along the path | ||
var newInner = new SimpleMap({key: "aloha"}); | ||
outer.set("inner", newInner); | ||
|
||
// Check that the observable has the new value | ||
assert.equal(canReflect.getValue(observable), "aloha", "observable updated"); | ||
}); | ||
|
||
onlyDevTest("works when the keys change - dependency data", function(assert) { | ||
var originalInner = new SimpleMap({key: "hello"}); | ||
var outer = new SimpleMap({inner: originalInner}); | ||
var observable = keyObservable(outer, "inner.key"); | ||
|
||
// The observable must be bound before it returns dependency data | ||
canReflect.onValue(observable, function() {}); | ||
|
||
// Change the value of a key along the path | ||
var newInner = new SimpleMap({key: "aloha"}); | ||
outer.set("inner", newInner); | ||
|
||
// Check the observable’s dependency data | ||
var observableDepData = canReflectDeps.getDependencyDataOf(observable); | ||
assert.deepEqual( | ||
observableDepData, | ||
{ | ||
whatChangesMe: { | ||
derive: { | ||
keyDependencies: new Map([ | ||
// the observable is changed by outer’s 'inner' property | ||
[outer, new Set(["inner"])], | ||
// the observable is changed by outer.inner’s 'key' property | ||
[newInner, new Set(["key"])] | ||
]) | ||
} | ||
}, | ||
whatIChange: { | ||
mutate: { | ||
keyDependencies: new Map([ | ||
// the observable changes outer.inner’s 'key' property | ||
[newInner, new Set(["key"])] | ||
]) | ||
} | ||
} | ||
}, | ||
"the observable has the correct mutation dependencies" | ||
); | ||
|
||
// Check outer.inner’s dependency data | ||
var innerDepData = canReflectDeps.getDependencyDataOf(outer, "inner"); | ||
assert.deepEqual( | ||
innerDepData, | ||
{ | ||
whatIChange: { | ||
derive: { | ||
// outer.inner changes the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
} | ||
}, | ||
"outer.inner has the correct mutation dependencies" | ||
); | ||
|
||
// Check the original outer.inner.key’s dependency data | ||
var originalKeyDepData = canReflectDeps.getDependencyDataOf(originalInner, "key"); | ||
assert.notOk( | ||
originalKeyDepData, | ||
"original outer.inner.key no longer has any dependencies" | ||
); | ||
|
||
// Check the new outer.inner.key’s dependency data | ||
var newKeyDepData = canReflectDeps.getDependencyDataOf(newInner, "key"); | ||
assert.deepEqual( | ||
newKeyDepData, | ||
{ | ||
whatChangesMe: { | ||
mutate: { | ||
// outer.inner.key has zero mutate.keyDependencies | ||
keyDependencies: new Map(), | ||
// outer.inner.key is changed by the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
}, | ||
whatIChange: { | ||
derive: { | ||
// outer.inner.key changes the observable | ||
valueDependencies: new Set([observable]) | ||
} | ||
} | ||
}, | ||
"new outer.inner.key has the correct mutation dependencies" | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
var canKey = require("can-key"); | ||
var canKeyUtils = require("can-key/utils"); | ||
var canReflect = require("can-reflect"); | ||
var Observation = require("can-observation"); | ||
|
||
//!steal-remove-start | ||
if (process.env.NODE_ENV !== 'production') { | ||
var canReflectDependencies = require("can-reflect-dependencies"); | ||
} | ||
//!steal-remove-end | ||
|
||
module.exports = function keyObservable(root, keyPath) { | ||
var keyPathParts = canKeyUtils.parts(keyPath); | ||
|
||
// Some variables used to build the dependency/mutation graph | ||
//!steal-remove-start | ||
if (process.env.NODE_ENV !== 'production') { | ||
var lastIndex = keyPathParts.length - 1; | ||
var lastKey;// This stores the last part of the keyPath, e.g. “key” in “outer.inner.key” | ||
var lastParent;// This stores the object that the last key is on, e.g. “outer.inner” in outer: {inner: {"key": "value"}} | ||
} | ||
//!steal-remove-end | ||
|
||
var observation = new Observation(function() { | ||
var value; | ||
|
||
// This needs to be walked every time because the objects along the key path might change | ||
canKey.walk(root, keyPathParts, function(keyData, i) { | ||
if (i === lastIndex) { | ||
//!steal-remove-start | ||
if (process.env.NODE_ENV !== 'production') { | ||
// observation is mutating keyData.parent | ||
if (lastParent && (keyData.key !== lastKey || keyData.parent !== lastParent)) { | ||
canReflectDependencies.deleteMutatedBy(lastParent, lastKey, observation); | ||
} | ||
lastKey = keyData.key; | ||
lastParent = keyData.parent; | ||
canReflectDependencies.addMutatedBy(lastParent, lastKey, observation); | ||
} | ||
//!steal-remove-end | ||
|
||
value = keyData.value; | ||
} | ||
}); | ||
|
||
return value; | ||
}); | ||
|
||
var symbolsToAssign = { | ||
"can.setValue": function(newVal) { | ||
canKey.set(root, keyPathParts, newVal); | ||
} | ||
}; | ||
|
||
//!steal-remove-start | ||
if (process.env.NODE_ENV !== 'production') { | ||
|
||
// Debug name | ||
symbolsToAssign["can.getName"] = function getName() { | ||
var objectName = canReflect.getName(root); | ||
return "keyObservable<" + objectName + "." + keyPath + ">"; | ||
}; | ||
|
||
// Register what this observable changes | ||
symbolsToAssign["can.getWhatIChange"] = function getWhatIChange() { | ||
return { | ||
mutate: { | ||
keyDependencies: new Map([ | ||
[lastParent, new Set([lastKey])] | ||
]) | ||
} | ||
}; | ||
}; | ||
} | ||
//!steal-remove-end | ||
|
||
return canReflect.assignSymbols(observation, symbolsToAssign); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters