can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
369 lines (283 loc) • 8.51 kB
JavaScript
/* jshint asi:true*/
steal("can/map", "can/compute", "can/test", "can/list", "steal-qunit", function(){
QUnit.module('can/map');
test("Basic Map", 4, function () {
var state = new can.Map({
category: 5,
productType: 4
});
state.bind("change", function (ev, attr, how, val, old) {
equal(attr, "category", "correct change name");
equal(how, "set");
equal(val, 6, "correct");
equal(old, 5, "correct");
});
state.attr("category", 6);
state.unbind("change");
});
test("Nested Map", 5, function () {
var me = new can.Map({
name: {
first: "Justin",
last: "Meyer"
}
});
ok(me.attr("name") instanceof can.Map);
me.bind("change", function (ev, attr, how, val, old) {
equal(attr, "name.first", "correct change name")
equal(how, "set")
equal(val, "Brian", "correct")
equal(old, "Justin", "correct")
})
me.attr("name.first", "Brian");
me.unbind("change")
})
test("remove attr", function () {
var state = new can.Map({
category: 5,
productType: 4
});
state.removeAttr("category");
deepEqual(can.Map.keys(state), ["productType"], "one property");
});
test("remove attr on key with dot", function () {
var state = new can.Map({
"key.with.dots": 12,
productType: 4
});
var state2 = new can.Map({
"key.with.dots": 4,
key: {
"with": {
someValue: 20
}
}
});
state.removeAttr("key.with.dots");
state2.removeAttr("key.with.someValue");
deepEqual( can.Map.keys(state), ["productType"], "one property");
deepEqual( can.Map.keys(state2), ["key.with.dots", "key"], "two properties");
deepEqual( can.Map.keys( state2.key["with"] ) , [], "zero properties");
});
test("nested event handlers are not run by changing the parent property (#280)", function () {
var person = new can.Map({
name: {
first: "Justin"
}
})
person.bind("name.first", function (ev, newName) {
ok(false, "name.first should never be called")
//equal(newName, "hank", "name.first handler called back with correct new name")
});
person.bind("name", function () {
ok(true, "name event triggered")
})
person.attr("name", {
first: "Hank"
});
});
test("cyclical objects (#521)", function () {
var foo = {};
foo.foo = foo;
var fooed = new can.Map(foo);
ok(true, "did not cause infinate recursion");
ok(fooed.attr('foo') === fooed, "map points to itself")
var me = {
name: "Justin"
}
var references = {
husband: me,
friend: me
}
var ref = new can.Map(references)
ok(ref.attr('husband') === ref.attr('friend'), "multiple properties point to the same thing")
})
test('Getting attribute that is a can.compute should return the compute and not the value of the compute (#530)', function () {
var compute = can.compute('before');
var map = new can.Map({
time: compute
});
equal(map.time, compute, 'dot notation call of time is compute');
equal(map.attr('time'), compute, '.attr() call of time is compute');
})
test('_cid add to original object', function () {
var map = new can.Map(),
obj = {
'name': 'thecountofzero'
};
map.attr('myObj', obj);
ok(!obj._cid, '_cid not added to original object');
})
test("can.each used with maps", function () {
can.each(new can.Map({
foo: "bar"
}), function (val, attr) {
if (attr === "foo") {
equal(val, "bar")
} else {
ok(false, "no properties other should be called " + attr)
}
})
})
test("can.Map serialize triggers reading (#626)", function () {
var old = can.__observe;
var attributesRead = [];
var readingTriggeredForKeys = false;
can.__observe = function (object, attribute) {
if (attribute === "__keys") {
readingTriggeredForKeys = true;
} else {
attributesRead.push(attribute);
}
};
var testMap = new can.Map({
cats: "meow",
dogs: "bark"
});
testMap.serialize();
ok(can.inArray("cats", attributesRead ) !== -1 && can.inArray( "dogs", attributesRead ) !== -1, "map serialization triggered __reading on all attributes");
ok(readingTriggeredForKeys, "map serialization triggered __reading for __keys");
can.__observe = old;
})
test("Test top level attributes", 7, function () {
var test = new can.Map({
'my.enable': false,
'my.item': true,
'my.count': 0,
'my.newCount': 1,
'my': {
'value': true,
'nested': {
'value': 100
}
}
});
equal(test.attr('my.value'), true, 'correct');
equal(test.attr('my.nested.value'), 100, 'correct');
ok(test.attr("my.nested") instanceof can.Map);
equal(test.attr('my.enable'), false, 'falsey (false) value accessed correctly');
equal(test.attr('my.item'), true, 'truthey (true) value accessed correctly');
equal(test.attr('my.count'), 0, 'falsey (0) value accessed correctly');
equal(test.attr('my.newCount'), 1, 'falsey (1) value accessed correctly');
});
test("computed properties don't cause memory leaks", function () {
var computeMap = can.Map.extend({
'name': can.compute(function(){
return this.attr('first') + this.attr('last')
})
}),
handler = function(){},
map = new computeMap({
first: 'Mickey',
last: 'Mouse'
});
map.bind('name', handler);
map.bind('name', handler);
equal(map._computedAttrs.name.count, 2, '2 handlers listening to computed property');
map.unbind('name', handler);
map.unbind('name', handler);
equal(map._computedAttrs.name.count, 0, '0 handlers listening to computed property');
});
test("serializing cycles", function(){
var map1 = new can.Map({name: "map1"});
var map2 = new can.Map({name: "map2"});
map1.attr("map2", map2);
map2.attr("map1", map1);
var res = map1.serialize();
equal(res.name, "map1");
equal(res.map2.name, "map2");
});
test("Unbinding from a map with no bindings doesn't throw an error (#1015)", function() {
expect(0);
var test = new can.Map({});
try {
test.unbind('change');
} catch(e) {
ok(false, 'No error should be thrown');
}
});
test("Fast dispatch event still has target and type (#1082)", 4, function() {
var data = new can.Map({
name: 'CanJS'
});
data.bind('change', function(ev){
equal(ev.type, 'change');
equal(ev.target, data);
});
data.bind('name', function(ev){
equal(ev.type, 'name');
equal(ev.target, data);
});
data.attr('name', 'David');
});
test("map passed to Map constructor (#1166)", function(){
var map = new can.Map({x: 1});
var res = new can.Map(map);
deepEqual(res.attr(), {
x: 1
}, "has the same properties");
});
test("constructor passed to scope is threated as a property (#1261)", function(){
var Constructor = can.Construct.extend({});
var Map = can.Map.extend({
Todo: Constructor
});
var m = new Map();
equal(m.attr("Todo"), Constructor);
});
test('_bindings count maintained after calling .off() on undefined property (#1490) ', function () {
var map = new can.Map({
test: 1
});
map.on('test', can.noop);
equal(map._bindings, 1, 'The number of bindings is correct');
map.off('undefined_property');
equal(map._bindings, 1, 'The number of bindings is still correct');
});
test("Should be able to get and set attribute named 'watch' on can.Map in Firefox", function() {
var map = new can.Map({});
map.attr("watch");
ok(true, "can have attribute named 'watch' on a can.Map instance");
});
test("Should be able to get and set attribute named 'unwatch' on can.Map in Firefox", function() {
var map = new can.Map({});
map.attr("unwatch");
ok(true, "can have attribute named 'unwatch' on a can.Map instance");
});
test('Creating map in compute dispatches all events properly', function() {
expect(2);
var source = can.compute(0);
var c = can.compute(function() {
var map = new can.Map();
source();
map.bind("foo", function(){
ok(true);
});
map.attr({foo: "bar"}); //DISPATCH
return map;
});
c.bind("change",function(){});
can.batch.start();
source(1);
can.batch.stop();
});
test('should get an empty string property value correctly', function() {
var map = new can.Map({
foo: 'foo',
'': 'empty string'
});
equal(map.attr(''), 'empty string');
});
test("can.Map::attr setting is observable", function() {
expect(0);
var c = can.compute(function() {
return new can.Map();
});
c.bind('change', function() {
ok(false, "the compute should not be updated");
});
var map = c();
// recomputes c
map.attr('foo', 'bar');
});
});