can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
458 lines (377 loc) • 11.6 kB
JavaScript
steal("can/view/scope", "can/route", "can/test", "steal-qunit", function () {
QUnit.module('can/view/scope');
test("basics",function(){
var items = new can.Map({ people: [{name: "Justin"},[{name: "Brian"}]], count: 1000 });
var itemsScope = new can.view.Scope(items),
arrayScope = new can.view.Scope(itemsScope.attr('people'), itemsScope),
firstItem = new can.view.Scope( arrayScope.attr('0'), arrayScope );
var nameInfo = firstItem.read('name');
deepEqual(nameInfo.reads, [{key: "name", at: false}]);
equal(nameInfo.scope, firstItem);
equal(nameInfo.value,"Justin");
equal(nameInfo.rootObserve, items.people[0]);
});
/*
* REMOVE
test("adding items",function(){
expect(1);
var base = new can.view.Scope({}),
cur = base.add(new can.Map());
cur._data.bind("items",function(ev, newVal, oldVal){
ok(newVal.length, "newVal is an array")
})
cur.attr("items",[1])
})(*/
/* test("current context",function(){
var base = new can.view.Scope({}),
cur = base.add("foo")
equal( cur.get(".").value, "foo", ". returns value");
equal( cur.attr("."), "foo", ". returns value");
equal( cur.get("this").value, "foo", "this returns value");
equal( cur.attr("this"), "foo", "this returns value");
})*/
/*test("highest scope observe is parent observe",function(){
var parent = new can.Map({name: "Justin"})
var child = new can.Map({vals: "something"})
var base = new can.view.Scope(parent),
cur = base.add(child);
var data = cur.get("bar")
equal(data.parent, parent, "gives highest parent observe")
equal(data.value, undefined, "no value")
})*/
/* test("computes on scope",function(){
var base = new can.view.Scope({}),
cur = base.add(can.compute({name: {first: "justin"}}));
var data = cur.get("name.first");
equal(data.value, "justin", "computes on path will be evaluted")
})*/
/* test("functions on an observe get called with this correctly", function(){
var Person = can.Map.extend({
fullName: function(){
equal( this.attr('first'), "Justin" )
}
})
var me = new Person({name: "Justin"})
var base = new can.view.Scope(me),
cur = base.add({other: "foo"})
var data = cur.get("fullName");
equal(data.value, Person.prototype.fullName, "got the raw function");
equal(data.parent, me, "parent provided")
})*/
test('can.view.Scope.prototype.computeData', function () {
var map = new can.Map();
var base = new can.view.Scope(map);
var age = base.computeData('age')
.compute;
equal(age(), undefined, 'age is not set');
age.bind('change', function (ev, newVal, oldVal) {
equal(newVal, 31, 'newVal is provided correctly');
equal(oldVal, undefined, 'oldVal is undefined');
});
age(31);
equal(map.attr('age'), 31, 'maps age is set correctly');
});
test('backtrack path (#163)', function () {
var row = new can.Map({
first: 'Justin'
}),
col = {
format: 'str'
}, base = new can.view.Scope(row),
cur = base.add(col);
equal(cur.attr('.'), col, 'got col');
equal(cur.attr('..'), row, 'got row');
equal(cur.attr('../first'), 'Justin', 'got row');
});
test('nested properties with compute', function () {
var me = new can.Map({
name: {
first: 'Justin'
}
});
var cur = new can.view.Scope(me);
var compute = cur.computeData('name.first')
.compute;
var changes = 0;
compute.bind('change', function (ev, newVal, oldVal) {
if (changes === 0) {
equal(oldVal, 'Justin');
equal(newVal, 'Brian');
} else if (changes === 1) {
equal(oldVal, 'Brian');
equal(newVal, undefined);
} else if (changes === 2) {
equal(oldVal, undefined);
equal(newVal, 'Payal');
} else if (changes === 3) {
equal(oldVal, 'Payal');
equal(newVal, 'Curtis');
}
changes++;
});
equal(compute(), 'Justin', 'read value after bind');
me.attr('name.first', 'Brian');
me.removeAttr('name');
me.attr('name', {
first: 'Payal'
});
me.attr('name', new can.Map({
first: 'Curtis'
}));
});
test('function at the end', function () {
var compute = new can.view.Scope({
me: {
info: function () {
return 'Justin';
}
}
})
.computeData('me.info')
.compute;
equal(compute(), 'Justin');
var fn = function () {
return this.name;
};
var compute2 = new can.view.Scope({
me: {
info: fn,
name: 'Hank'
}
})
.computeData('me.info', {
isArgument: true,
args: []
})
.compute;
equal(compute2()(), 'Hank');
});
test('binds to the right scope only', function () {
var baseMap = new can.Map({
me: {
name: {
first: 'Justin'
}
}
});
var base = new can.view.Scope(baseMap);
var topMap = new can.Map({
me: {
name: {}
}
});
var scope = base.add(topMap);
var compute = scope.computeData('me.name.first')
.compute;
compute.bind('change', function (ev, newVal, oldVal) {
equal(oldVal, 'Justin');
equal(newVal, 'Brian');
});
equal(compute(), 'Justin');
// this should do nothing
topMap.attr('me.name.first', 'Payal');
baseMap.attr('me.name.first', 'Brian');
});
test('Scope read returnObserveMethods=true', function () {
var MapConstruct = can.Map.extend({
foo: function (arg) {
equal(this, data.map, 'correct this');
equal(arg, true, 'correct arg');
}
});
var data = {
map: new MapConstruct()
};
var res = can.view.Scope.read(data, can.compute.read.reads('map.foo'), {
isArgument: true
});
res.value(true);
});
test('rooted observable is able to update correctly', function () {
var baseMap = new can.Map({
name: {
first: 'Justin'
}
});
var scope = new can.view.Scope(baseMap);
var compute = scope.computeData('name.first')
.compute;
equal(compute(), 'Justin');
baseMap.attr('name', new can.Map({
first: 'Brian'
}));
equal(compute(), 'Brian');
});
test('computeData reading an object with a compute', function () {
var sourceAge = 21;
var age = can.compute(function (newVal) {
if (newVal) {
sourceAge = newVal;
} else {
return sourceAge;
}
});
var scope = new can.view.Scope({
person: {
age: age
}
});
var computeData = scope.computeData('person.age');
var value = computeData.compute();
equal(value, 21, 'correct value');
computeData.compute(31);
equal(age(), 31, 'age updated');
});
test('computeData with initial empty compute (#638)', function () {
expect(2);
var compute = can.compute();
var scope = new can.view.Scope({
compute: compute
});
var computeData = scope.computeData('compute');
equal(computeData.compute(), undefined);
computeData.compute.bind('change', function (ev, newVal) {
equal(newVal, 'compute value');
});
compute('compute value');
});
test('Can read static properties on constructors (#634)', function () {
can.Map.extend('can.Foo', {
static_prop: 'baz'
}, {
proto_prop: 'thud'
});
var data = new can.Foo({
own_prop: 'quux'
}),
scope = new can.view.Scope(data);
equal(scope.computeData('constructor.static_prop')
.compute(), 'baz', 'static prop');
});
test("Can read static properties on constructors (#634)", function () {
can.Map.extend("can.Foo", {
static_prop: "baz"
}, {
proto_prop: "thud"
});
var data = new can.Foo({
own_prop: "quux"
}),
scope = new can.view.Scope(data);
equal(scope.computeData("constructor.static_prop")
.compute(), "baz", "static prop");
});
test('Scope lookup restricted to current scope with ./ (#874)', function() {
var current;
var scope = new can.view.Scope(
new can.Map({value: "A Value"})
).add(
current = new can.Map({})
);
var compute = scope.computeData('./value').compute;
equal(compute(), undefined, "no initial value");
compute.bind("change", function(ev, newVal){
equal(newVal, "B Value", "changed");
});
compute("B Value");
equal(current.attr("value"), "B Value", "updated");
});
test('reading properties on undefined (#1314)', function(){
var scope = new can.view.Scope(undefined);
var compute = scope.compute("property");
equal(compute(), undefined, "got back undefined");
});
test("Scope attributes can be set (#1297, #1304)", function(){
var comp = can.compute('Test');
var map = new can.Map({
other: {
name: "Justin"
}
});
var scope = new can.view.Scope({
name: "Matthew",
other: {
person: {
name: "David"
},
comp: comp
}
});
scope.attr("name", "Wilbur");
equal(scope.attr("name"), "Wilbur", "Value updated");
scope.attr("other.person.name", "Dave");
equal(scope.attr("other.person.name"), "Dave", "Value updated");
scope.attr("other.comp", "Changed");
equal(comp(), "Changed", "Compute updated");
scope = new can.view.Scope(map);
scope.attr("other.name", "Brian");
equal(scope.attr("other.name"), "Brian", "Value updated");
equal(map.attr("other.name"), "Brian", "Name update in map");
});
test("computeData.compute get/sets computes in maps", function(){
var compute = can.compute(4);
var map = new can.Map();
map.attr("computer", compute);
var scope = new can.view.Scope(map);
var computeData = scope.computeData("computer",{});
equal( computeData.compute(), 4, "got the value");
computeData.compute(5);
equal(compute(), 5, "updated compute value");
equal( computeData.compute(), 5, "the compute has the right value");
});
test("computesData can find update when initially undefined parent scope becomes defined (#579)", function(){
expect(2);
var map = new can.Map();
var scope = new can.view.Scope(map);
var top = scope.add(new can.Map());
var computeData = top.computeData("value",{});
equal( computeData.compute(), undefined, "initially undefined");
computeData.compute.bind("change", function(ev, newVal){
equal(newVal, "first");
});
map.attr("value","first");
});
test("A scope's %root is the last context", function(){
var map = new can.Map();
var refs = can.view.Scope.refsScope();
// Add a bunch of contexts onto the scope, we want to make sure we make it to
// the top.
var scope = refs.add(map).add(new can.view.Scope.Refs()).add(new can.Map());
var root = scope.attr("%root");
ok(!(root instanceof can.view.Scope.Refs), "root isn't a reference");
equal(root, map, "The root is the map passed into the scope");
});
test("can set scope attributes with ../ (#2132)", function(){
var map = new can.Map();
var scope = new can.view.Scope(map);
var top = scope.add(new can.Map());
top.attr("../foo", "bar");
equal(map.attr("foo"), "bar");
});
test("can read parent context with ../ (#2244)", function(){
var map = new can.Map();
var scope = new can.view.Scope(map);
var top = scope.add(new can.Map());
equal( top.attr("../"), map, "looked up value correctly");
});
test("fast path scope computes can be unbound and then bound (#2305)", function(){
var map = new can.Map({prop: "value"});
var scope = new can.view.Scope(map);
var compute = scope.computeData("prop").compute;
var fn = function(){};
compute.bind("change", fn);
compute.unbind("change", fn);
compute.bind("change", function(ev, newVal){
equal(newVal, "value2", "got new value in change event");
});
map.attr("prop","value2");
});
test("that .set with ../ is able to skip notContext scopes (#43)", function(){
var instance = new can.Map({prop: 0});
var notContextContext = {NAME: "NOT CONTEXT"};
var top = {NAME: "TOP"};
var scope = new can.view.Scope(instance).add(notContextContext,{notContext: true}).add(top);
scope.set("../prop",1);
equal( instance.prop, 1);
});
});