can-define
Version:
Create observable objects with JS dot operator compatibility
424 lines (334 loc) • 9.74 kB
JavaScript
var DefineMap = require("can-define/map/map");
var DefineList = require("can-define/list/list");
var canReflect = require("can-reflect");
var isPlainObject = canReflect.isPlainObject;
var Observation = require("can-observation");
var define = require("can-define");
var QUnit = require("steal-qunit");
QUnit.module("can-define: map and list combined");
QUnit.test("basics", function(assert) {
var items = new DefineMap({ people: [{name: "Justin"},{name: "Brian"}], count: 1000 });
assert.ok(items.people instanceof DefineList, "people is list");
assert.ok(items.people.item(0) instanceof DefineMap, "1st object is Map");
assert.ok(items.people.item(1) instanceof DefineMap, "2nd object is Map");
assert.equal(items.people.item(1).name, "Brian", "2nd object's name is right");
assert.equal(items.count, 1000, "count is number");
});
QUnit.test("basic type", function(assert) {
assert.expect(6);
var Typer = function(arrayWithAddedItem, listWithAddedItem) {
this.arrayWithAddedItem = arrayWithAddedItem;
this.listWithAddedItem = listWithAddedItem;
};
define(Typer.prototype, {
arrayWithAddedItem: {
type: function(value) {
if (value && value.push) {
value.push("item");
}
return value;
}
},
listWithAddedItem: {
type: function(value) {
if (value && value.push) {
value.push("item");
}
return value;
},
Type: DefineList
}
});
var t = new Typer();
assert.deepEqual(Object.keys(t), [], "no keys");
var array = [];
t.arrayWithAddedItem = array;
assert.deepEqual(array, ["item"], "updated array");
assert.equal(t.arrayWithAddedItem, array, "leave value as array");
t.listWithAddedItem = [];
assert.ok(t.listWithAddedItem instanceof DefineList, "convert to CanList");
assert.equal(t.listWithAddedItem[0], "item", "has item in it");
var observation = new Observation(function() {
return t.listWithAddedItem.attr("length");
});
canReflect.onValue(observation, function(newVal) {
assert.equal(newVal, 2, "got a length change");
});
t.listWithAddedItem.push("another item");
});
QUnit.test("serialize works", function(assert) {
var Person = DefineMap.extend({
first: "string",
last: "string"
});
var People = DefineList.extend({
"*": Person
});
var people = new People([{first: "j", last: "m"}]);
assert.deepEqual(people.serialize(), [{first: "j", last: "m"}]);
});
QUnit.test("Extended Map with empty def converts to default Observables", function(assert) {
var School = DefineMap.extend({
students: {},
teacher: {}
});
var school = new School();
school.students = [{name: "J"}];
school.teacher = {name: "M"};
assert.ok(school.students instanceof DefineList, "converted to DefineList");
assert.ok(school.teacher instanceof DefineMap, "converted to DefineMap");
});
QUnit.test("default 'observable' type prevents Type from working (#29)", function(assert) {
var M = DefineMap.extend("M",{
id: "number"
});
var L = DefineList.extend("L",{
"*": M
});
var MyMap = DefineMap.extend({
l: L
});
var m = new MyMap({
l: [{id: 5}]
});
assert.ok( m.l[0] instanceof M, "is instance" );
assert.equal(m.l[0].id, 5, "correct props");
});
QUnit.test("inline DefineList Type", function(assert) {
var M = DefineMap.extend("M",{
id: "number"
});
var MyMap = DefineMap.extend({
l: {Type: [M]}
});
var m = new MyMap({
l: [{id: 5}]
});
assert.ok( m.l[0] instanceof M, "is instance" );
assert.equal(m.l[0].id, 5, "correct props");
});
QUnit.test("recursively `get`s (#31)", function(assert) {
var M = DefineMap.extend("M",{
id: "number"
});
var MyMap = DefineMap.extend({
l: {Type: [M]}
});
var m = new MyMap({
l: [{id: 5}]
});
var res = m.get();
assert.ok( Array.isArray(res.l), "is a plain array");
assert.ok( isPlainObject(res.l[0]), "plain object");
});
QUnit.test("DefineList trigger deprecation warning when set with Map.set (#93)", function(assert) {
assert.expect(0);
var map = new DefineMap({
things: [{ foo: 'bar' }]
});
map.things.attr = function(){
assert.ok(false, "attr should not be called");
};
map.assign({ things: [{ baz: 'luhrmann' }] });
});
QUnit.test("Value generator can read other properties", function(assert) {
var Map = define.Constructor({
letters: {
default: "ABC"
},
numbers: {
default: [1, 2, 3]
},
definedLetters: {
default: 'DEF'
},
definedNumbers: {
default: [4, 5, 6]
},
generatedLetters: {
default: function() {
return 'GHI';
}
},
generatedNumbers: {
default: function() {
return new DefineList([7, 8, 9]);
}
},
// Get prototype defaults
firstLetter: {
default: function() {
return this.letters.substr(0, 1);
}
},
firstNumber: {
default: function() {
return this.numbers[0];
}
},
// Get defined simple `value` defaults
middleLetter: {
default: function() {
return this.definedLetters.substr(1, 1);
}
},
middleNumber: {
default: function() {
return this.definedNumbers[1];
}
},
// Get defined `value` function defaults
lastLetter: {
default: function() {
return this.generatedLetters.substr(2, 1);
}
},
lastNumber: {
default: function() {
return this.generatedNumbers[2];
}
}
});
var map = new Map();
var prefix = 'Was able to read dependent value from ';
assert.equal(map.firstLetter, 'A',
prefix + 'traditional can.Map style property definition');
assert.equal(map.firstNumber, 1,
prefix + 'traditional can.Map style property definition');
assert.equal(map.middleLetter, 'E',
prefix + 'define plugin style default property definition');
assert.equal(map.middleNumber, 5,
prefix + 'define plugin style default property definition');
assert.equal(map.lastLetter, 'I',
prefix + 'define plugin style generated default property definition');
assert.equal(map.lastNumber, 9,
prefix + 'define plugin style generated default property definition');
});
QUnit.test('value and get (#1521)', function(assert) {
// problem here is that previously, can.Map would set `size:1` on
// the map. This would effectively set the "lastSetValue".
// in this new version, default values are not set. They
// are only present. later.
// one option is that there's a "read-mode" for last-set. Until it's
// been set, it should get it's value from any default value?
var MyMap = define.Constructor({
data: {
default: function() {
return new DefineList(['test']);
}
},
size: {
default: 1,
get: function(val) {
var list = this.data;
var length = list.attr('length');
return val + length;
}
}
});
var map = new MyMap({});
assert.equal(map.size, 2);
});
QUnit.test('Assign value on map', function(assert) {
var MyConstruct = DefineMap.extend({
list: DefineList,
name: 'string'
});
var obj = new MyConstruct({
list: ['data', 'data', 'data'],
name: 'CanJS',
foo: {
bar: 'bar',
zoo: 'say'
}
});
obj.assign({
list: ['another'],
foo: {
bar: 'zed'
}
});
assert.equal(obj.list.length, 1, 'list length should be 1');
assert.propEqual(obj.foo, { bar: 'zed' }, 'foo.bar is set correctly');
assert.equal(obj.name, 'CanJS', 'name is unchanged');
});
QUnit.test('Update value on a map', function(assert) {
var MyConstruct = DefineMap.extend({
list: DefineList,
name: 'string'
});
var obj = new MyConstruct({
list: ['data', 'data', 'data'],
name: 'CanJS',
foo: {
bar: 'bar'
}
});
obj.update({
list: ['another'],
foo: {
bar: 'zed'
}
});
assert.equal(obj.list.length, 1, 'list length should be 1');
assert.equal(obj.foo.bar, 'zed', 'foo.bar is set correctly');
assert.equal(obj.name, undefined, 'name is removed');
});
QUnit.test('Deep assign a map', function(assert) {
var MyConstruct = DefineMap.extend({
list: DefineList,
name: 'string'
});
var obj = new MyConstruct({
list: ['data', 'data', 'data'],
name: 'Test Name'
});
assert.equal(obj.list.length, 3, 'list length should be 3');
obj.assignDeep({
list: ['something']
});
assert.equal(obj.name, 'Test Name', 'Name property is still intact');
assert.equal(obj.list[0], 'something', 'the first element in the list should be updated');
});
QUnit.test('Deep updating a map', function(assert) {
var MyConstruct = DefineMap.extend({
list: DefineList,
name: 'string'
});
var obj = new MyConstruct({
list: ['data', 'data', 'data'],
name: 'Test Name'
});
assert.equal(obj.list.length, 3, 'list length should be 3');
obj.updateDeep({
list: ['something']
});
assert.equal(obj.name, undefined, 'Name property has been reset');
assert.equal(obj.list[0], 'something', 'the first element of the list should be updated');
});
QUnit.test("assignDeep", function(assert) {
var justin = new DefineMap({name: "Justin", age: 35}),
payal = new DefineMap({name: "Payal", age: 35});
var people = new DefineList([justin, payal]);
people.assignDeep([
{age: 36}
]);
assert.deepEqual(people.serialize(), [
{name: "Justin", age: 36},
{name: "Payal", age: 35}
], "assigned right");
});
QUnit.test("DefineMap fires 'set' event when a new property is added (#400)", function(assert) {
var counter = 0;
var vm = new DefineMap({});
canReflect.onPatches(vm, function (patch) {
if (counter === 0) {
assert.equal(patch[0].type, 'add', 'dispatched add correctly');
} else {
assert.equal(patch[0].type, 'set', 'dispatched set correctly');
}
counter++;
});
vm.set("name", "Matt");
vm.set("name", "Justin");
});