can-define
Version:
Create observable objects with JS dot operator compatibility
1,549 lines (1,248 loc) • 40.7 kB
JavaScript
;
var QUnit = require("steal-qunit");
var DefineList = require("can-define/list/list");
var DefineMap = require("can-define/map/map");
var Observation = require("can-observation");
var define = require("can-define");
var canReflect = require("can-reflect");
var canSymbol = require("can-symbol");
var dev = require("can-log/dev/dev");
var canTestHelpers = require("can-test-helpers/lib/dev");
var assign = require("can-assign");
QUnit.module("can-define/list/list");
QUnit.test("List is an event emitter", function(assert) {
var Base = DefineList.extend({});
assert.ok(Base.on, 'Base has event methods.');
var List = Base.extend({});
assert.ok(List.on, 'List has event methods.');
});
QUnit.test("creating an instance", function(assert) {
var list = new DefineList([ "a", "b", "c" ]);
list.on("add", function(ev, newVals, index) {
assert.deepEqual(newVals, [ "d" ]);
assert.equal(index, 3);
});
list.push("d");
});
QUnit.test('list attr changes length', function(assert) {
var l = new DefineList([
0,
1,
2
]);
l.set(3, 3);
assert.equal(l.length, 4);
});
QUnit.test('remove on pop', function(assert) {
var l = new DefineList([ 0, 1, 2 ]);
l.pop();
assert.equal(l.length, 2);
assert.deepEqual(l.get(), [ 0, 1 ]);
});
QUnit.test('list splice', function(assert) {
var l = new DefineList([
0,
1,
2,
3
]);
l.on('add', function(ev, newVals, index) {
assert.deepEqual(newVals, [
'a',
'b'
], 'got the right newVals');
assert.equal(index, 1, 'adding items');
});
l.on('remove', function(ev, oldVals, index) {
assert.deepEqual(oldVals, [
1,
2
], 'got the right oldVals');
assert.equal(index, 1, 'no new Vals');
});
l.splice(1, 2, 'a', 'b');
assert.deepEqual(l.get(), [
0,
'a',
'b',
3
], 'serialized');
});
QUnit.test('Array accessor methods', function(assert) {
assert.expect(11);
var l = new DefineList([
'a',
'b',
'c'
]),
sliced = l.slice(2),
joined = l.join(' | '),
concatenated = l.concat([
2,
1
], new DefineList([ 0 ]));
assert.ok(sliced instanceof DefineList, 'Slice is an Observable list');
assert.equal(sliced.length, 1, 'Sliced off two elements');
assert.equal(sliced[0], 'c', 'Single element as expected');
assert.equal(joined, 'a | b | c', 'Joined list properly');
assert.ok(concatenated instanceof DefineList, 'Concatenated is an Observable list');
assert.deepEqual(concatenated.serialize(), [
'a',
'b',
'c',
2,
1,
0
], 'DefineList concatenated properly');
l.forEach(function(letter, index) {
assert.ok(true, 'Iteration');
if (index === 0) {
assert.equal(letter, 'a', 'First letter right');
}
if (index === 2) {
assert.equal(letter, 'c', 'Last letter right');
}
});
});
QUnit.test('Concatenated list items Equal original', function(assert) {
var l = new DefineList([
{ firstProp: "Some data" },
{ secondProp: "Next data" }
]),
concatenated = l.concat([
{ hello: "World" },
{ foo: "Bar" }
]);
assert.ok(l[0] === concatenated[0], "They are Equal");
assert.ok(l[1] === concatenated[1], "They are Equal");
});
QUnit.test('Lists with maps concatenate properly', function(assert) {
var Person = DefineMap.extend();
var People = DefineList.extend({
'#': Person
});
var Genius = Person.extend();
var Animal = DefineMap.extend();
var me = new Person({ name: "John" });
var animal = new Animal({ name: "Tak" });
var genius = new Genius({ name: "Einstein" });
var hero = { name: "Ghandi" };
var people = new People([]);
var specialPeople = new People([
genius,
hero
]);
people = people.concat([ me, animal, specialPeople ], specialPeople, [ 1, 2 ], 3);
assert.ok(people.length === 8, "List length is right");
assert.ok(people[0] === me, "Map in list === vars created before concat");
assert.ok(people[1] instanceof Person, "Animal got serialized to Person");
});
QUnit.test('splice removes items in IE (#562)', function(assert) {
var l = new DefineList([ 'a' ]);
l.splice(0, 1);
assert.ok(!l.get(0), 'all props are removed');
});
QUnit.test('reverse triggers add/remove events (#851)', function(assert) {
assert.expect(4);
var l = new DefineList([ 1, 2, 3 ]);
l.on('add', function() {
assert.ok(true, 'add called');
});
l.on('remove', function() {
assert.ok(true, 'remove called');
});
l.on('length', function() {
assert.ok(true, 'length should be called');
});
l.reverse();
assert.deepEqual(l.get(), [ 3, 2, 1 ], "reversed");
});
QUnit.test('filter', function(assert) {
var l = new DefineList([ { id: 1, name: "John" }, { id: 2, name: "Mary" } ]);
var filtered = l.filter(function(item) {
return item.name === "Mary";
});
assert.notDeepEqual(filtered, l, "not same object");
assert.equal(filtered.length, 1, "one item");
assert.equal(filtered[0].name, "Mary", "filter works");
});
QUnit.test('No Add Events if DefineList Splice adds the same items that it is removing. (#1277, #1399)', function(assert) {
var list = new DefineList([ "a", "b" ]);
list.bind('add', function() {
assert.ok(false, 'Add callback should not be called.');
});
list.bind('remove', function() {
assert.ok(false, 'Remove callback should not be called.');
});
var result = list.splice(0, 2, "a", "b");
assert.deepEqual(result, [ "a", "b" ]);
});
QUnit.test("add event always returns an array as the value (#998)", function(assert) {
var list = new DefineList([]),
msg;
list.bind("add", function(ev, newElements, index) {
assert.deepEqual(newElements, [ 4 ], msg);
});
msg = "works on push";
list.push(4);
list.pop();
msg = "works on attr()";
list.set(0, 4);
list.pop();
msg = "works on replace()";
list.replace([ 4 ]);
});
QUnit.test("Setting with .set() out of bounds of length triggers add event with leading undefineds", function(assert) {
var list = new DefineList([ 1 ]);
list.bind("add", function(ev, newElements, index) {
assert.deepEqual(newElements, [ undefined, undefined, 4 ],
"Leading undefineds are included");
assert.equal(index, 1, "Index takes into account the leading undefineds from a .set()");
});
list.set(3, 4);
});
QUnit.test("No events should fire if removals happened on empty arrays", function(assert) {
var list = new DefineList([]),
msg;
list.bind("remove", function(ev, removed, index) {
assert.ok(false, msg);
});
msg = "works on pop";
list.pop();
msg = "works on shift";
list.shift();
assert.ok(true, "No events were fired.");
});
QUnit.test('setting an index out of bounds does not create an array', function(assert) {
assert.expect(1);
var l = new DefineList();
l.set('1', 'foo');
assert.equal(l.get('1'), 'foo');
});
QUnit.test('splice with similar but less items works (#1606)', function(assert) {
var list = new DefineList([ 'aa', 'bb', 'cc' ]);
list.splice(0, list.length, 'aa', 'cc', 'dd');
assert.deepEqual(list.get(), [ 'aa', 'cc', 'dd' ]);
list.splice(0, list.length, 'aa', 'cc');
assert.deepEqual(list.get(), [ 'aa', 'cc' ]);
});
QUnit.test('filter returns same list type (#1744)', function(assert) {
var ParentList = DefineList.extend();
var ChildList = ParentList.extend();
var children = new ChildList([ 1, 2, 3 ]);
assert.ok(children.filter(function() {}) instanceof ChildList);
});
QUnit.test('reverse returns the same list instance (#1744)', function(assert) {
var ParentList = DefineList.extend();
var ChildList = ParentList.extend();
var children = new ChildList([ 1, 2, 3 ]);
assert.ok(children.reverse() === children);
});
QUnit.test("slice and join are observable by a compute (#1884)", function(assert) {
assert.expect(2);
var list = new DefineList([ 1, 2, 3 ]);
var sliced = new Observation(function() {
return list.slice(0, 1);
});
canReflect.onValue(sliced, function(newVal){
assert.deepEqual(newVal.get(), [ 2 ], "got a new DefineList");
});
var joined = new Observation(function() {
return list.join(",");
});
canReflect.onValue(joined, function(newVal){
assert.equal(newVal, "2,3", "joined is observable");
});
list.shift();
});
QUnit.test('list.replace', function(assert) {
var firstArray = [
{ id: 1, name: "Marshall" },
{ id: 2, name: "Austin" },
{ id: 3, name: "Hyrum" }
];
var myList = new DefineList(firstArray);
var newArray = [
{ id: 4, name: "Aubree" },
{ id: 5, name: "Leah" },
{ id: 6, name: 'Lily' }
];
myList.replace(newArray);
assert.equal(myList.length, 3);
assert.equal(myList[0].name, "Aubree");
assert.equal(myList[1].name, "Leah");
assert.equal(myList[2].name, "Lily", "Can replace a List with an Array.");
myList.replace(firstArray);
assert.equal(myList.length, 3);
assert.equal(myList[0].name, "Marshall");
assert.equal(myList[1].name, "Austin");
assert.equal(myList[2].name, "Hyrum", "Can replace a List with another List.");
});
QUnit.test('list.map', function(assert) {
var myArray = [
{ id: 1, name: "Marshall" },
{ id: 2, name: "Austin" },
{ id: 3, name: "Hyrum" }
];
var myList = new DefineList(myArray);
var newList = myList.map(function(person) {
person.lastName = "Thompson";
return person;
});
assert.equal(newList.length, 3);
assert.equal(newList[0].name, "Marshall");
assert.equal(newList[0].lastName, "Thompson");
assert.equal(newList[1].name, "Austin");
assert.equal(newList[1].lastName, "Thompson");
assert.equal(newList[2].name, "Hyrum");
assert.equal(newList[2].lastName, "Thompson");
var ExtendedList = DefineList.extend({
testMe: function() {
return "It Worked!";
}
});
var myExtendedList = new ExtendedList(myArray);
var newExtendedList = myExtendedList.map(function(person) {
person.lastName = "Thompson";
return person;
});
try {
newExtendedList.testMe();
} catch(err) {
assert.ok(err.message.match(/testMe/), 'Does not return the same type of list.');
}
});
QUnit.test('list.sort a simple list', function(assert) {
var myList = new DefineList([
"Marshall",
"Austin",
"Hyrum"
]);
myList.sort();
assert.equal(myList.length, 3);
assert.equal(myList[0], "Austin");
assert.equal(myList[1], "Hyrum");
assert.equal(myList[2], "Marshall", "Basic list was properly sorted.");
});
QUnit.test('list.sort a list of objects', function(assert) {
var objList = new DefineList([
{ id: 1, name: "Marshall" },
{ id: 2, name: "Austin" },
{ id: 3, name: "Hyrum" }
]);
objList.sort(function(a, b) {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
assert.equal(objList.length, 3);
assert.equal(objList[0].name, "Austin");
assert.equal(objList[1].name, "Hyrum");
assert.equal(objList[2].name, "Marshall", "List of objects was properly sorted.");
});
QUnit.test('list.sort a list of objects without losing reference (#137)', function(assert) {
var unSorted = new DefineList([ { id: 3 }, { id: 2 }, { id: 1 } ]);
var sorted = unSorted.slice(0).sort(function(a, b) {
return a.id > b.id ? 1 : (a.id < b.id ? -1 : 0);
});
assert.equal(unSorted[0], sorted[2], 'items should be equal');
});
QUnit.test("list defines", function(assert) {
assert.expect(6);
var Todo = function(props) {
assign(this, props);
//CID(this);
};
define(Todo.prototype, {
completed: "boolean",
destroyed: {
default: false
}
});
Todo.prototype.destroy = function() {
this.destroyed = true;
};
var TodoList = DefineList.extend({
"*": Todo,
remaining: {
get: function() {
return this.filter({
completed: false
});
}
},
completed: {
get: function() {
return this.filter({
completed: true
});
}
},
destroyCompleted: function() {
this.completed.forEach(function(todo) {
todo.destroy();
});
},
setCompletedTo: function(value) {
this.forEach(function(todo) {
todo.completed = value;
});
}
});
var todos = new TodoList([ { completed: true }, { completed: false } ]);
assert.ok(todos.item(0) instanceof Todo, "correct instance");
assert.equal(todos.completed.length, 1, "only one todo");
todos.on("completed", function(ev, newVal, oldVal) {
assert.ok(newVal instanceof TodoList, "right type");
assert.equal(newVal.length, 2, "all items");
assert.ok(oldVal instanceof TodoList, "right type");
assert.equal(oldVal.length, 1, "all items");
});
todos.setCompletedTo(true);
});
QUnit.test("extending the base supports overwriting _eventSetup", function(assert) {
var L = DefineList.extend({});
Object.getOwnPropertyDescriptor(DefineMap.prototype, "_eventSetup");
L.prototype.arbitraryProp = true;
assert.ok(true, "set arbitraryProp");
L.prototype._eventSetup = function() {};
assert.ok(true, "worked");
});
QUnit.test("setting expandos on a DefineList", function(assert) {
var DL = DefineList.extend({
count: "number"
});
var dl = new DL();
dl.assign({ count: 5, skip: 2 });
assert.equal(dl.get("count"), 5, "read with .get defined"); //-> 5
assert.equal(dl.count, 5, "read with . defined");
assert.equal(dl.get("skip"), 2, "read with .get expando");
assert.equal(dl.skip, 2, "read with . expando");
assert.equal(dl.get("limit"), undefined, "read with .get undefined");
});
QUnit.test("passing a DefineList to DefineList (#33)", function(assert) {
var m = new DefineList([ {}, {} ]);
var m2 = new DefineList(m);
assert.deepEqual(m.get(), m2.get());
assert.ok(m[0] === m2[0], "index the same");
assert.ok(m[1] === m2[1], "index the same");
});
QUnit.test("reading and setting expandos", function(assert) {
var list = new DefineList();
var countObservation = new Observation(function() {
return list.get("count");
}, null, function(newValue) {
assert.equal(newValue, 1000, "got new value");
});
countObservation.start();
list.set("count", 1000);
assert.equal(countObservation.value, 1000);
var list2 = new DefineList();
list2.on("count", function(ev, newVal) {
assert.equal(newVal, 5);
});
list2.set("count", 5);
});
QUnit.test("extending DefineList constructor functions (#61)", function(assert) {
var AList = DefineList.extend('AList', { aProp: {}, aMethod: function() {} });
var BList = AList.extend('BList', { bProp: {}, bMethod: function() {} });
var CList = BList.extend('CList', { cProp: {}, cMethod: function() {} });
var list = new CList([ {}, {} ]);
list.on("aProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "PROP");
assert.equal(oldVal, undefined);
});
list.on("bProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "FOO");
assert.equal(oldVal, undefined);
});
list.on("cProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "BAR");
assert.equal(oldVal, undefined);
});
list.aProp = "PROP";
list.bProp = 'FOO';
list.cProp = 'BAR';
assert.ok(list.aMethod);
assert.ok(list.bMethod);
assert.ok(list.cMethod);
});
QUnit.test("extending DefineList constructor functions more than once (#61)", function(assert) {
var AList = DefineList.extend("AList", { aProp: {}, aMethod: function() {} });
var BList = AList.extend("BList", { bProp: {}, bMethod: function() {} });
var CList = AList.extend("CList", { cProp: {}, cMethod: function() {} });
var list1 = new BList([ {}, {} ]);
var list2 = new CList([ {}, {}, {} ]);
list1.on("aProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "PROP", "aProp newVal on list1");
assert.equal(oldVal, undefined);
});
list1.on("bProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "FOO", "bProp newVal on list1");
assert.equal(oldVal, undefined);
});
list2.on("aProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "PROP", "aProp newVal on list2");
assert.equal(oldVal, undefined);
});
list2.on("cProp", function(ev, newVal, oldVal) {
assert.equal(newVal, "BAR", "cProp newVal on list2");
assert.equal(oldVal, undefined);
});
list1.aProp = "PROP";
list1.bProp = 'FOO';
list2.aProp = "PROP";
list2.cProp = 'BAR';
assert.ok(list1.aMethod, "list1 aMethod");
assert.ok(list1.bMethod);
assert.ok(list2.aMethod);
assert.ok(list2.cMethod, "list2 cMethod");
});
QUnit.test("extending DefineList constructor functions - value (#61)", function(assert) {
var AList = DefineList.extend("AList", { aProp: { default: 1 } });
var BList = AList.extend("BList", { });
var CList = BList.extend("CList", { });
var c = new CList([]);
assert.equal(c.aProp, 1, "got initial value");
});
QUnit.test("'*' inheritance works (#61)", function(assert) {
var Account = DefineMap.extend({
name: "string",
amount: "number",
slug: {
serialize: true,
get: function() {
return this.name.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
}
}
});
var BaseList = DefineList.extend({
"*": Account
});
var ExtendedList = BaseList.extend({});
var xl = new ExtendedList([ {} ]);
assert.ok(xl[0] instanceof Account);
});
QUnit.test("shorthand getter setter (#56)", function(assert) {
var People = DefineList.extend({
first: "*",
last: "*",
get fullName() {
return this.first + " " + this.last;
},
set fullName(newVal) {
var parts = newVal.split(" ");
this.first = parts[0];
this.last = parts[1];
}
});
var p = new People([]);
p.fullName = "Mohamed Cherif";
p.on("fullName", function(ev, newVal, oldVal) {
assert.equal(oldVal, "Mohamed Cherif");
assert.equal(newVal, "Justin Meyer");
});
assert.equal(p.fullName, "Mohamed Cherif", "fullName initialized right");
p.fullName = "Justin Meyer";
});
QUnit.test("added and removed are called after items are added/removed (#14)", function(assert) {
var Person = DefineMap.extend({
id: "number",
name: "string"
});
var addedFuncCalled, removedFuncCalled, theList;
var People = DefineList.extend({
"#": {
added: function(items, index) {
addedFuncCalled = true;
assert.ok(items, "items added got passed to added");
assert.ok(typeof index === 'number', "index of items was passed to added and is a number");
assert.ok(items[0].name === 'John', "Name was correct");
theList = this;
},
removed: function(items, index) {
removedFuncCalled = true;
assert.ok(items, "items added got passed to removed");
assert.ok(typeof index === 'number', "index of items was passed to removed and is a number");
theList = this;
},
Type: Person
},
outsideProp: {
type: "boolean",
default: true
}
});
var people = new People([]);
var me = new Person();
me.name = "John";
me.id = "1234";
assert.ok(!addedFuncCalled, "added function has not been called yet");
people.push(me);
assert.ok(addedFuncCalled, "added function was called");
assert.ok(theList.outsideProp === true && theList instanceof People,
"the list was passed correctly as this to added");
theList = null;
assert.ok(!removedFuncCalled, "removed function has not been called yet");
people.splice(people.indexOf(me), 1);
assert.ok(removedFuncCalled, "removed function was called");
assert.ok(theList.outsideProp === true && theList instanceof People,
"the list was passed correctly as this to removed");
});
QUnit.test("* vs # (#78)", function(assert) {
var MyList = DefineList.extend({
"*": "number",
"#": {
added: function() {
assert.ok(true, "called on init");
},
removed: function() {},
type: "string"
}
});
var list = new MyList([ 1, 2, 3 ]);
assert.ok(list[0] === "1", "converted to string");
list.set("prop", "4");
assert.ok(list.prop === 4, "type converted");
});
QUnit.test("Array shorthand uses #", function(assert) {
var MyMap = DefineMap.extend({
"numbers": [ "number" ]
});
var map = new MyMap({ numbers: [ "1", "2" ] });
assert.ok(map.numbers[0] === 1, "converted to number");
map.numbers.set("prop", "4");
assert.ok(map.numbers.prop === "4", "type left alone");
});
QUnit.test("replace-with-self lists are diffed properly (can-view-live#10)", function(assert) {
var a = new DefineMap({ name: "A" });
var b = new DefineMap({ name: "B" });
var c = new DefineMap({ name: "C" });
var d = new DefineMap({ name: "D" });
assert.expect(4);
var list1 = new DefineList([ a, b ]);
list1.on("add", function(ev, newVals, where) {
throw new Error("list1 should not add.");
});
list1.on("remove", function(ev, oldVals, where) {
throw new Error("list1 should not remove.");
});
list1.replace([ a, b ]);
var list2 = new DefineList([ a, b, c ]);
list2.on("add", function(ev, newVals, where) {
assert.equal(newVals.length, 1, "list2 added length");
assert.equal(where, 2, "list2 added location");
});
list2.on("remove", function(ev, oldVals, where) {
assert.equal(oldVals.length, 1, "list2 removed length");
assert.equal(where, 2, "list2 removed location");
});
list2.replace([ a, b, d ]);
});
QUnit.test("set >= length - triggers length event (#152)", function(assert) {
var l = new DefineList([ 1, 2, 3 ]);
var batchNum = null;
l.on("add", function(e) {
assert.ok(true, "add called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
l.on("remove", function(e) {
assert.ok(false, "remove called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
l.on("length", function(e) {
assert.ok(true, "length called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
assert.expect(4);
l.set(3, 5);
assert.deepEqual(l.get(), [ 1, 2, 3, 5 ], "updated list");
});
QUnit.test("set < length - triggers length event (#150)", function(assert) {
var l = new DefineList([ 1, 2, 3 ]);
var batchNum = null;
l.on("add", function(e) {
assert.ok(true, "add called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
l.on("remove", function(e) {
assert.ok(true, "remove called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
l.on("length", function(e) {
assert.ok(true, "length called");
if (batchNum === null) {
batchNum = e.batchNum;
}
else {
assert.equal(batchNum, e.batchNum, "batch numbers match");
}
});
assert.expect(6);
l.set(2, 4);
assert.deepEqual(l.get(), [ 1, 2, 4 ], "updated list");
});
QUnit.test("set/splice are observable", function(assert) {
var list = new DefineList([ 1, 2, 3, 4, 5 ]);
var count = new Observation(function() {
var count = 0;
for (var i = 0; i < list.length; i++) {
count += (list[i] % 2) ? 1 : 0;
}
return count;
});
canReflect.onValue(count, function(){
assert.ok(true);
});
assert.expect(3);
list.set(3, 5);
list.set(2, 4);
list.splice(1, 1, 1);
});
QUnit.test("setting length > current (#147)", function(assert) {
var list = new DefineList([ 1, 2 ]);
list.length = 5;
assert.equal(list.length, 5);
assert.equal(list.hasOwnProperty(0), true);
assert.equal(list.hasOwnProperty(1), true);
assert.equal(list.hasOwnProperty(2), true);
assert.equal(list.hasOwnProperty(3), true);
assert.equal(list.hasOwnProperty(4), true);
assert.equal(list.hasOwnProperty(5), false);
});
QUnit.test("setting length < current (#147)", function(assert) {
var list = new DefineList([ 1, 2, 3, 4, 5 ]);
list.length = 3;
assert.equal(list.length, 3);
assert.equal(list.hasOwnProperty(0), true);
assert.equal(list.hasOwnProperty(1), true);
assert.equal(list.hasOwnProperty(2), true);
assert.equal(list.hasOwnProperty(3), false);
assert.equal(list.hasOwnProperty(4), false);
assert.equal(list.hasOwnProperty(5), false);
});
QUnit.test('every', function(assert) {
var l = new DefineList([ { id: 1, name: "Bob" }, { id: 2, name: "Bob" } ]);
var allBobs = l.every(function(item) {
return item.name === "Bob";
});
assert.ok(allBobs, "Every works in true case");
var idOne = l.every(function(item) {
return item.id === 1;
});
assert.ok(!idOne, "Every works in false case");
allBobs = l.every({
name : "Bob"
});
assert.ok(allBobs, "Every works in true case");
idOne = l.every({
name : "Bob",
id : 1
});
assert.ok(!idOne, "Every works in false case");
});
QUnit.test('some', function(assert) {
var l = new DefineList([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } ]);
var allBobs = l.some(function(item) {
return item.name === "Bob";
});
assert.ok(allBobs, "Some works in true case");
var idOne = l.some(function(item) {
return item.name === "Charlie";
});
assert.ok(!idOne, "Some works in false case");
allBobs = l.some({
name : "Bob"
});
assert.ok(allBobs, "Some works in true case");
idOne = l.some({
name : "Bob",
id : 1
});
assert.ok(!idOne, "Some works in false case");
});
QUnit.test('lastIndexOf', function(assert) {
var l = new DefineList([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } ]);
var bobIdx = l.lastIndexOf(l[1]);
assert.equal(bobIdx, 1, "lastIndexOf found object");
var charlieIdx = l.lastIndexOf({ id : 3, name: "Charlie" });
assert.equal(charlieIdx, -1, "lastIndexOf not found object");
// make a new reference to [1] at [2]
l.push(l[1]);
bobIdx = l.lastIndexOf(l[1]);
assert.equal(bobIdx, 2, "lastIndexOf found last index of duped object");
});
QUnit.test('reduce', function(assert) {
var l = new DefineList([
{ id: 1, name: "Alice", score: 10 },
{ id: 2, name: "Bob", score: 20 }
]);
var totalScores = l.reduce(function(total, player) {
return total + player.score;
}, 0);
assert.equal(totalScores, 30, "Reduce works over list");
});
QUnit.test('reduceRight', function(assert) {
var l = new DefineList([
{ id: 1, name: "Alice"},
{ id: 2, name: "Bob"}
]);
var concatenatedNames = l.reduceRight(function(string, person) {
return string + person.name;
}, "");
assert.equal(concatenatedNames, "BobAlice", "ReduceRight works over list");
});
/*
// TODO: bring these back with can-stache-key
test("compute(defineMap, 'property.names') works (#20)", function(){
var map = new DefineMap();
var c = compute(map, "foo.bar");
c.on("change", function(ev, newVal){
assert.equal(newVal, 2);
});
map.set("foo", new DefineMap());
map.foo.set("bar", 2);
});
test("compute(DefineList, 0) works (#17)", function(assert){
assert.expect(1);
var list = new DefineList([1,2,3]);
var c = compute(list, 0);
c.on("change", function(ev, newVal){
assert.equal(newVal, 5);
});
list.set(0, 5);
});
QUnit.test("can-reflect onValue", function(assert) {
assert.expect(1);
var list = new DefineList([1,2,3]);
var first = compute(list, 0);
canReflect.onValue(first, function(newVal) {
assert.equal(newVal, 5);
});
list.set(0, 5);
});
*/
QUnit.test("can-reflect onKeyValue", function(assert) {
assert.expect(3);
var list = new DefineList([1,2,3]);
var key = 1;
canReflect.onKeyValue(list, key, function(newVal) {
assert.equal(newVal, 5);
});
list.set(key, 5);
canReflect.onKeyValue(list, 'length', function(newVal) {
assert.equal(newVal, 4);
});
list.push(6);
});
QUnit.test("works with can-reflect", function(assert) {
var a = new DefineMap({ foo: 4 });
var b = new DefineList([ "foo", "bar" ]);
var c;
assert.equal( canReflect.getKeyValue(b, "0"), "foo", "unbound value");
assert.ok(!canReflect.isValueLike(b), "isValueLike is false");
assert.ok(canReflect.isObservableLike(b), "isObservableLike is true");
assert.ok(canReflect.isMapLike(b), "isMapLike is true");
assert.ok(canReflect.isListLike(b), "isListLike is false");
assert.ok( !canReflect.keyHasDependencies(b, "length"), "keyHasDependencies -- false");
define(c = Object.create(b), {
length: {
get: function() {
return a.foo;
}
}
});
assert.ok(canReflect.getKeyDependencies(c, "length"), "dependencies exist");
assert.ok(
canReflect.getKeyDependencies(c, "length").valueDependencies.has(c._computed.length.compute),
"dependencies returned"
);
/*
canReflect.onKeysAdded(b, handler);
canReflect.onKeysRemoved(b, handler);
assert.ok(b.__bindEvents.add, "add handler added");
assert.ok(b.__bindEvents.remove, "remove handler added");
b.push("quux");
c.push("quux");
assert.equal( canReflect.getKeyValue(c, "length"), "4", "bound value");
b.pop();*/
});
QUnit.test("can-reflect setKeyValue", function(assert) {
var a = new DefineList([ "a", "b" ]);
canReflect.setKeyValue(a, 1, "c");
assert.equal(a[1], "c", "setKeyValue");
});
QUnit.test("can-reflect deleteKeyValue", function(assert) {
var a = new DefineList([ "a", "b" ]);
a.set("foo", "bar");
canReflect.deleteKeyValue(a, 0);
assert.equal(a[1], undefined, "last value is now undefined");
assert.equal(a[0], "b", "last value is shifted down");
canReflect.deleteKeyValue(a, "foo");
assert.equal(a.foo, undefined, "value not included in serial");
assert.ok(!("foo" in a.get()), "value not included in serial");
});
QUnit.test("can-reflect getKeyDependencies", function(assert) {
var a = new DefineMap({ foo: 4 });
var b = new DefineList([ "foo", "bar" ]);
var c;
assert.ok(!canReflect.getKeyDependencies(b, "length"), "No dependencies before binding");
define(c = Object.create(b), {
length: {
get: function() {
return a.foo;
}
}
});
assert.ok(canReflect.getKeyDependencies(c, "length"), "dependencies exist");
assert.ok(canReflect.getKeyDependencies(c, "length").valueDependencies.has(c._computed.length.compute), "dependencies returned");
});
QUnit.test("assign property", function(assert) {
var list = new DefineList(["A","B"]);
list.assign({count: 0, skip: 2, arr: ['1', '2', '3']});
assert.equal(list.get('count'), 0, 'Count set properly');
list.assign({count: 1000, arr: ['first']});
assert.deepEqual(list.get('arr'), new DefineList(['first']), 'Array is set properly');
assert.equal(list.get('count'), 1000, 'Count set properly');
assert.equal(list.get('skip'), 2, 'Skip is unchanged');
});
QUnit.test("update property", function(assert) {
var list = new DefineList(["A","B"]);
list.update({count: 0, skip: 2});
assert.equal(list.get('count'), 0, 'Count set properly');
list.update({count: 1000});
assert.equal(list.get('count'), 1000, 'Count set properly');
assert.equal(list.get('skip'), undefined, 'Skip is changed');
});
QUnit.test("assignDeep property", function(assert) {
var list = new DefineList(["A","B"]);
list.assignDeep({count: 0, skip: 2, foo: { bar: 'zed', tar: 'yap' }});
assert.equal(list.get('count'), 0, 'Count set properly');
list.assignDeep({count: 1000, foo: {bar: 'updated'}});
assert.equal(list.get('count'), 1000, 'Count set properly');
assert.equal(list.get('skip'), 2, 'Skip is unchanged');
assert.propEqual(list.get('foo'), { bar: 'updated', tar: 'yap' }, 'Foo was updated properly');
});
QUnit.test("updateDeep property", function(assert) {
var list = new DefineList(["A","B"]);
list.updateDeep({count: 0, skip: 2, foo: { bar: 'zed', tar: 'yap' }});
assert.equal(list.get('count'), 0, 'Count set properly');
list.updateDeep({count: 1000});
assert.equal(list.get('count'), 1000, 'Count set properly');
assert.equal(list.get('skip'), undefined, 'Skip is set to undefined');
assert.propEqual(list.get('foo'), undefined, 'Foo is set to undefined');
});
QUnit.test("registered symbols", function(assert) {
var a = new DefineMap({ "a": "a" });
assert.ok(a[canSymbol.for("can.isMapLike")], "can.isMapLike");
assert.equal(a[canSymbol.for("can.getKeyValue")]("a"), "a", "can.getKeyValue");
a[canSymbol.for("can.setKeyValue")]("a", "b");
assert.equal(a.a, "b", "can.setKeyValue");
function handler(val) {
assert.equal(val, "c", "can.onKeyValue");
}
a[canSymbol.for("can.onKeyValue")]("a", handler);
a.a = "c";
a[canSymbol.for("can.offKeyValue")]("a", handler);
a.a = "d"; // doesn't trigger handler
});
QUnit.test("cannot remove length", function(assert) {
var list = new DefineList(["a"]);
list.set("length", undefined);
assert.equal(list.length, 1, "list length is unchanged");
});
QUnit.test("cannot set length to a non-number", function(assert) {
var list = new DefineList(["a"]);
list.set("length", null);
assert.equal(list.length, 1, "list length is unchanged");
list.set("length", "foo");
assert.equal(list.length, 1, "list length is unchanged");
list.set("length", {});
assert.equal(list.length, 1, "list length is unchanged");
});
QUnit.test("_length is not enumerable", function(assert) {
assert.ok(!Object.getOwnPropertyDescriptor(new DefineList(), "_length").enumerable, "_length is not enumerable");
});
QUnit.test("update with no indexed items sets length to 0", function(assert) {
var list = new DefineList(["a"]);
assert.equal(list.length, 1, "list length is correct before update");
list.update({ foo: "bar" });
assert.equal(list.length, 0, "list length is correct after update");
});
["length", "_length"].forEach(function(prop) {
QUnit.test("setting " + prop + " does not overwrite definition", function(assert) {
var list = new DefineList();
list.get(prop);
var proto = list, listDef, listDef2;
while(!listDef && proto) {
listDef = Object.getOwnPropertyDescriptor(proto, prop);
proto = Object.getPrototypeOf(proto);
}
list.set(prop, 1);
proto = list;
while(!listDef2 && proto) {
listDef2 = Object.getOwnPropertyDescriptor(proto, prop);
proto = Object.getPrototypeOf(proto);
}
delete listDef2.value;
delete listDef.value;
assert.deepEqual(listDef2, listDef, "descriptor hasn't changed");
});
});
QUnit.test("iterator can recover from bad _length", function(assert) {
var list = new DefineList(["a"]);
list.set("_length", null);
assert.equal(list._length, null, "Bad value for _length");
var iterator = list[canSymbol.iterator]();
var iteration = iterator.next();
assert.ok(iteration.done, "Didn't fail");
});
QUnit.test("onPatches", function(assert) {
var list = new DefineList(["a","b"]);
var PATCHES = [
[ {deleteCount: 2, index: 0, type: "splice"} ],
[ {index: 0, insert: ["A","B"], deleteCount: 0, type: "splice"} ]
];
var calledPatches = [];
var handler = function patchesHandler(patches){
calledPatches.push(patches);
};
list[canSymbol.for("can.onPatches")](handler,"notify");
list.replace(["A","B"]);
list[canSymbol.for("can.offPatches")](handler,"notify");
list.replace(["1","2"]);
assert.deepEqual(calledPatches, PATCHES);
});
canTestHelpers.devOnlyTest("can.getName symbol behavior", function(assert) {
var getName = function(instance) {
return instance[canSymbol.for("can.getName")]();
};
assert.ok(
"DefineList[]", getName(new DefineList()),
"should use DefineList constructor name by default"
);
var MyList = DefineList.extend("MyList", {});
assert.ok(
"MyList[]", getName(new MyList()),
"should use custom list name when provided"
);
});
QUnit.test("length event should include previous value", function(assert) {
var done = assert.async();
var list = new DefineList([]);
var other = new DefineList(["a"]);
var changes = [];
list.on("length", function(_, current, previous) {
changes.push({ current: current, previous: previous });
});
list.push("x");
list.pop();
list.push("y", "z");
list.splice(2, 0, "x", "w");
list.splice(0, 1);
list.sort();
list.replace(other);
assert.expect(1);
setTimeout(function() {
assert.deepEqual(
changes,
[
{ current: 1, previous: 0 },
{ current: 0, previous: 1 },
{ current: 2, previous: 0 },
{ current: 4, previous: 2 },
{ current: 3, previous: 4 },
{ current: 3, previous: 3 },
{ current: 1, previous: 3 }
],
"should include length before mutation"
);
done();
});
});
canTestHelpers.devOnlyTest("log all events", function(assert) {
var done = assert.async();
var list = new DefineList(["a","b", "c"]);
list.set("total", 100);
list.log();
var keys = [];
var log = dev.log;
dev.log = function() {
keys.push(JSON.parse(arguments[2]));
};
list.push("x");
list.pop();
list.set("total", 50);
assert.expect(1);
setTimeout(function() {
dev.log = log;
assert.deepEqual(
keys,
["add", "length", "remove", "length", "total"],
"should log 'add', 'remove', 'length' and 'propertyName' events"
);
done();
});
});
canTestHelpers.devOnlyTest("log single events", function(assert) {
var done = assert.async();
var list = new DefineList(["a","b", "c"]);
list.set("total", 100);
list.log("length");
var keys = [];
var log = dev.log;
dev.log = function() {
keys.push(JSON.parse(arguments[2]));
};
list.push("x");
list.pop();
list.set("total", 50);
assert.expect(1);
setTimeout(function() {
dev.log = log;
assert.deepEqual(keys, ["length", "length"], "should log 'length' event");
done();
});
});
canTestHelpers.devOnlyTest("log multiple events", function(assert) {
var done = assert.async();
var list = new DefineList(["a","b", "c"]);
list.set("total", 100);
list.log("add");
list.log("total");
var keys = [];
var log = dev.log;
dev.log = function() {
keys.push(JSON.parse(arguments[2]));
};
list.push("x");
list.pop();
list.set("total", 50);
assert.expect(1);
setTimeout(function() {
dev.log = log;
assert.deepEqual(keys, ["add", "total"], "should log add and total");
done();
});
});
QUnit.test("DefineList has defineInstanceKey symbol", function(assert) {
var Type = DefineList.extend({});
Type[canSymbol.for("can.defineInstanceKey")]("prop", {type: "number"});
var t = new Type();
t.prop = "5";
assert.equal(t.prop, 5, "value set");
});
QUnit.test(".sort() produces patches (can-stache#498)", function(assert) {
var list = new DefineList(["b","a"]);
var PATCHES = [
[ {index: 0, deleteCount: 2, type: "splice"}],
[ {index: 0, insert: ["a","b"], deleteCount: 0, type: "splice"} ]
];
var calledPatches = [];
var handler = function patchesHandler(patches){
calledPatches.push(patches);
};
list[canSymbol.for("can.onPatches")](handler,"notify");
list.sort();
assert.deepEqual(calledPatches, PATCHES);
});
QUnit.test("canReflect.getSchema", function(assert) {
var MyType = DefineMap.extend({
id: {identity: true, type: "number"},
name: "string"
});
var MyList = DefineList.extend({
count: "number",
"#": MyType
});
var schema = canReflect.getSchema(MyList);
assert.equal(schema.values, MyType);
});
QUnit.test("Bound serialized lists update when they change length", function(assert) {
assert.expect(1);
var list = new DefineList(["eggs"]);
var obs = new Observation(function(){
return list.serialize();
});
function onChange(val) {
assert.deepEqual(val, ["eggs", "toast"]);
}
canReflect.onValue(obs, onChange);
list.push("toast");
canReflect.offValue(obs, onChange);
});
if (typeof Array.prototype.includes === "function") {
QUnit.test("'includes' method basics (#277)", function(assert) {
assert.expect(6);
var emptyList = new DefineList([]);
assert.notOk(emptyList.includes(2));
var list = new DefineList([1, 2, 3]);
assert.ok(list.includes(2));
assert.notOk(list.includes(4));
assert.notOk(list.includes(3, 3));
assert.ok(list.includes(3, -1));
var nanList = new DefineList([1, 2, NaN]);
assert.ok(nanList.includes(NaN));
});
QUnit.test("'fromIndex' is not >= to the array length", function(assert) {
assert.expect(2);
var list = new DefineList(["a", "b", "c"]);
assert.notOk(list.includes("c", 3));
assert.notOk(list.includes("c", 100));
});
QUnit.test("computed index is less than 0", function(assert) {
assert.expect(4);
var list = new DefineList(["a", "b", "c"]);
assert.ok(list.includes("a", -100));
assert.ok(list.includes("b", -100));
assert.ok(list.includes("c", -100));
assert.notOk(list.includes("a", -2));
});
QUnit.test("Bound 'includes' (#277)", function(assert) {
assert.expect(1);
var list = new DefineList();
var obs = new Observation(function(){
return list.includes("foo");
});
function onChange(val) {
assert.ok(val);
}
canReflect.onValue(obs, onChange);
list.push("foo");
canReflect.offValue(obs, onChange);
});
}
QUnit.test("Set __inSetup prop #421", function(assert) {
var list = new DefineList([]);
list.set("__inSetup", "nope");
assert.equal(list.__inSetup, "nope");
});