can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
397 lines (339 loc) • 9.32 kB
JavaScript
steal("can/util", "can/list", "can/test", "can/compute", "steal-qunit", function(){
QUnit.module('can/list');
test('list attr changes length', function () {
var l = new can.List([
0,
1,
2
]);
l.attr(3, 3);
equal(l.length, 4);
});
test('removeAttr on list', function() {
var l = new can.List([0, 1, 2]);
l.removeAttr(1);
equal(l.attr('length'), 2);
deepEqual(l.attr(), [0, 2]);
});
test('list splice', function () {
var l = new can.List([
0,
1,
2,
3
]),
first = true;
l.bind('change', function (ev, attr, how, newVals, oldVals) {
equal(attr, '1');
if (first) {
equal(how, 'remove', 'removing items');
equal(newVals, undefined, 'no new Vals');
} else {
deepEqual(newVals, [
'a',
'b'
], 'got the right newVals');
equal(how, 'add', 'adding items');
}
first = false;
});
l.splice(1, 2, 'a', 'b');
deepEqual(l.serialize(), [
0,
'a',
'b',
3
], 'serialized');
});
test('list pop', function () {
var l = new can.List([
0,
1,
2,
3
]);
l.bind('change', function (ev, attr, how, newVals, oldVals) {
equal(attr, '3');
equal(how, 'remove');
equal(newVals, undefined);
deepEqual(oldVals, [3]);
});
l.pop();
deepEqual(l.serialize(), [
0,
1,
2
]);
});
test('remove nested property in item of array map', function () {
var state = new can.List([{
nested: true
}]);
state.bind('change', function (ev, attr, how, newVal, old) {
equal(attr, '0.nested');
equal(how, 'remove');
deepEqual(old, true);
});
state.removeAttr('0.nested');
equal(undefined, state.attr('0.nested'));
});
test('pop unbinds', function () {
var l = new can.List([{
foo: 'bar'
}]);
var o = l.attr(0),
count = 0;
l.bind('change', function (ev, attr, how, newVal, oldVal) {
count++;
if (count === 1) {
equal(attr, '0.foo', 'count is set');
} else if (count === 2) {
equal(how, 'remove');
equal(attr, '0');
} else {
ok(false, 'called too many times');
}
});
equal(o.attr('foo'), 'bar');
o.attr('foo', 'car');
l.pop();
o.attr('foo', 'bad');
});
test('splice unbinds', function () {
var l = new can.List([{
foo: 'bar'
}]);
var o = l.attr(0),
count = 0;
l.bind('change', function (ev, attr, how, newVal, oldVal) {
count++;
if (count === 1) {
equal(attr, '0.foo', 'count is set');
} else if (count === 2) {
equal(how, 'remove');
equal(attr, '0');
} else {
ok(false, 'called too many times');
}
});
equal(o.attr('foo'), 'bar');
o.attr('foo', 'car');
l.splice(0, 1);
o.attr('foo', 'bad');
});
test('always gets right attr even after moving array items', function () {
var l = new can.List([{
foo: 'bar'
}]);
var o = l.attr(0);
l.unshift('A new Value');
l.bind('change', function (ev, attr, how) {
equal(attr, '1.foo');
});
o.attr('foo', 'led you');
});
test('Array accessor methods', 11, function () {
var l = new can.List([
'a',
'b',
'c'
]),
sliced = l.slice(2),
joined = l.join(' | '),
concatenated = l.concat([
2,
1
], new can.List([0]));
ok(sliced instanceof can.List, 'Slice is an Observable list');
equal(sliced.length, 1, 'Sliced off two elements');
equal(sliced[0], 'c', 'Single element as expected');
equal(joined, 'a | b | c', 'Joined list properly');
ok(concatenated instanceof can.List, 'Concatenated is an Observable list');
deepEqual(concatenated.serialize(), [
'a',
'b',
'c',
2,
1,
0
], 'List concatenated properly');
l.forEach(function (letter, index) {
ok(true, 'Iteration');
if (index === 0) {
equal(letter, 'a', 'First letter right');
}
if (index === 2) {
equal(letter, 'c', 'Last letter right');
}
});
});
test('splice removes items in IE (#562)', function () {
var l = new can.List(['a']);
l.splice(0, 1);
ok(!l.attr(0), 'all props are removed');
});
test('list sets up computed attributes (#790)', function() {
var List = can.List.extend({
i: can.compute(0),
a: 0
});
var l = new List([1]);
equal(l.attr('i'), 0);
var Map = can.Map.extend({
f: can.compute(0)
});
var m = new Map();
m.attr('f');
});
test('reverse triggers add/remove events (#851)', function() {
expect(6);
var l = new can.List([1,2,3]);
l.bind('change', function() { ok(true, 'change should be called'); });
l.bind('set', function() { ok(false, 'set should not be called'); });
l.bind('add', function() { ok(true, 'add called'); });
l.bind('remove', function() { ok(true, 'remove called'); });
l.bind('length', function() { ok(true, 'length should be called'); });
l.reverse();
});
test('filter', function(){
var l = new can.List([{id: 1, name: "John"}, {id: 2, name: "Mary"}]);
var filtered = l.filter(function(item){
return item.name === "Mary";
});
notEqual(filtered._cid, l._cid, "not same object");
equal(filtered.length, 1, "one item");
equal(filtered[0].name, "Mary", "filter works");
});
test('removing expandos on lists', function(){
var list = new can.List(["a","b"]);
list.removeAttr("foo");
equal(list.length, 2);
});
test('No Add Events if List Splice adds the same items that it is removing. (#1277, #1399)', function() {
var list = new can.List(["a","b"]);
list.bind('add', function() {
ok(false, 'Add callback should not be called.');
});
list.bind('remove', function() {
ok(false, 'Remove callback should not be called.');
});
var result = list.splice(0, 2, "a", "b");
deepEqual(result, ["a", "b"]);
});
test("add event always returns an array as the value (#998)", function() {
var list = new can.List([]),
msg;
list.bind("add", function(ev, newElements, index) {
deepEqual(newElements, [4], msg);
});
msg = "works on push";
list.push(4);
list.pop();
msg = "works on attr()";
list.attr(0, 4);
list.pop();
msg = "works on replace()";
list.replace([4]);
});
test("Setting with .attr() out of bounds of length triggers add event with leading undefineds", function() {
var list = new can.List([1]);
list.bind("add", function(ev, newElements, index) {
deepEqual(newElements, [undefined, undefined, 4],
"Leading undefineds are included");
equal(index, 1, "Index takes into account the leading undefineds from a .attr()");
});
list.attr(3, 4);
});
test("No events should fire if removals happened on empty arrays", function() {
var list = new can.List([]),
msg;
list.bind("remove", function(ev, removed, index) {
ok(false, msg);
});
msg = "works on pop";
list.pop();
msg = "works on shift";
list.shift();
ok(true, "No events were fired.");
});
test('setting an index out of bounds does not create an array', function() {
expect(1);
var l = new can.List();
l.attr('1', 'foo');
equal(l.attr('1'), 'foo');
});
test('splice with similar but less items works (#1606)', function() {
var list = new can.List([ 'aa', 'bb', 'cc']);
list.splice(0, list.length, 'aa', 'cc', 'dd');
deepEqual(list.attr(), ['aa', 'cc', 'dd']);
list.splice(0, list.length, 'aa', 'cc');
deepEqual(list.attr(), ['aa', 'cc']);
});
test('filter returns same list type (#1744)', function() {
var ParentList = can.List.extend();
var ChildList = ParentList.extend();
var children = new ChildList([1,2,3]);
ok(children.filter(function() {}) instanceof ChildList);
});
test('reverse returns the same list instance (#1744)', function() {
var ParentList = can.List.extend();
var ChildList = ParentList.extend();
var children = new ChildList([1,2,3]);
ok(children.reverse() === children);
});
test("slice and join are observable by a compute (#1884)", function(){
expect(2);
var list = new can.List([1,2,3]);
var sliced = can.compute(function(){
return list.slice(0,1);
});
var joined = can.compute(function(){
return list.join(",");
});
sliced.bind("change", function(ev, newVal){
deepEqual(newVal.attr(), [2], "got a new can.List");
});
joined.bind("change", function(ev, newVal){
equal(newVal, "2,3", "joined is observable");
});
list.shift();
});
test("list is always updated with the last promise passed to replace (#2136)", function(){
var list = new can.List();
stop();
list.replace( new can.Deferred( function( def ) {
setTimeout( function(){
def.resolve([ "A" ]);
setTimeout(function(){
equal(list.attr(0), "B", "list set to last promise's value");
start();
},10);
}, 20 );
}));
list.replace( new can.Deferred( function( def ) {
setTimeout( function(){
def.resolve([ "B" ]);
}, 10 );
}));
});
test('filter with context', function(){
var l = new can.List([{id: 1}]);
var context = {};
var contextWasCorrect = false;
l.filter(function(){
contextWasCorrect = (this === context);
return true;
}, context);
equal(contextWasCorrect, true, "context was correctly passed");
});
test('map with context', function(){
var l = new can.List([{id: 1}]);
var context = {};
var contextWasCorrect = false;
l.map(function(){
contextWasCorrect = (this === context);
return true;
}, context);
equal(contextWasCorrect, true, "context was correctly passed");
});
});