1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-14 07:02:34 +01:00
invoiceninja/public/vendor/knockout-mapping/spec/mappingBehaviors.js
2014-04-22 14:33:53 +03:00

1815 lines
42 KiB
JavaScript

module('Mapping');
test('ko.mapping.toJS should unwrap observable values', function () {
var atomicValues = ["hello", 123, true, null, undefined,
{
a: 1
}];
for (var i = 0; i < atomicValues.length; i++) {
var data = ko.observable(atomicValues[i]);
var result = ko.mapping.toJS(data);
equal(ko.isObservable(result), false);
deepEqual(result, atomicValues[i]);
}
});
test('ko.mapping.toJS should unwrap observable properties, including nested ones', function () {
var data = {
a: ko.observable(123),
b: {
b1: ko.observable(456),
b2: 789
}
};
var result = ko.mapping.toJS(data);
equal(result.a, 123);
equal(result.b.b1, 456);
equal(result.b.b2, 789);
});
test('ko.mapping.toJS should unwrap observable arrays and things inside them', function () {
var data = ko.observableArray(['a', 1,
{
someProp: ko.observable('Hey')
}]);
var result = ko.mapping.toJS(data);
equal(result.length, 3);
equal(result[0], 'a');
equal(result[1], 1);
equal(result[2].someProp, 'Hey');
});
test('ko.mapping.toJS should ignore specified single property', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.toJS(data, { ignore: "b" });
equal(result.a, "a");
equal(result.b, undefined);
});
test('ko.mapping.toJS should ignore specified single property on update', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
var result = ko.mapping.fromJS(data);
equal(result.a(), "a");
equal(result.b(), "b");
equal(result.c(), "c");
ko.mapping.fromJS({ a: "a2", b: "b2", c: "c2" }, { ignore: ["b", "c"] }, result);
equal(result.a(), "a2");
equal(result.b(), "b");
equal(result.c(), "c");
});
test('ko.mapping.toJS should ignore specified multiple properties', function() {
var data = {
a: { a1: "a1", a2: "a2" },
b: "b"
};
var result = ko.mapping.fromJS(data, { ignore: ["a.a1", "b"] });
equal(result.a.a1, undefined);
equal(result.a.a2(), "a2");
equal(result.b, undefined);
data.a.a1 = "a11";
data.a.a2 = "a22";
ko.mapping.fromJS(data, {}, result);
equal(result.a.a1, undefined);
equal(result.a.a2(), "a22");
equal(result.b, undefined);
});
test('ko.mapping.fromJS should ignore specified single property', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { ignore: "b" });
equal(result.a(), "a");
equal(result.b, undefined);
});
test('ko.mapping.fromJS should ignore specified array item', function() {
var data = {
a: "a",
b: [{ b1: "v1" }, { b2: "v2" }]
};
var result = ko.mapping.fromJS(data, { ignore: "b[1].b2" });
equal(result.a(), "a");
equal(result.b()[0].b1(), "v1");
equal(result.b()[1].b2, undefined);
});
test('ko.mapping.fromJS should ignore specified single property, also when going back .toJS', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { ignore: "b" });
var js = ko.mapping.toJS(result);
equal(js.a, "a");
equal(js.b, undefined);
});
test('ko.mapping.fromJS should copy specified single property', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { copy: "b" });
equal(result.a(), "a");
equal(result.b, "b");
});
test('ko.mapping.fromJS should copy specified array', function() {
var data = {
a: "a",
b: ["b1", "b2"]
};
var result = ko.mapping.fromJS(data, { copy: "b" });
equal(result.a(), "a");
deepEqual(result.b, ["b1", "b2"]);
});
test('ko.mapping.fromJS should copy specified array item', function() {
var data = {
a: "a",
b: [{ b1: "v1" }, { b2: "v2" }]
};
var result = ko.mapping.fromJS(data, { copy: "b[0].b1" });
equal(result.a(), "a");
equal(result.b()[0].b1, "v1");
equal(result.b()[1].b2(), "v2");
});
test('ko.mapping.fromJS should copy specified single property, also when going back .toJS', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { copy: "b" });
var js = ko.mapping.toJS(result);
equal(js.a, "a");
equal(js.b, "b");
});
test('ko.mapping.fromJS should copy specified single property, also when going back .toJS, except when overridden', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { copy: "b" });
var js = ko.mapping.toJS(result, { ignore: "b" });
equal(js.a, "a");
equal(js.b, undefined);
});
test('ko.mapping.toJS should include specified single property', function() {
var data = {
a: "a"
};
var mapped = ko.mapping.fromJS(data);
mapped.c = 1;
mapped.d = 2;
var result = ko.mapping.toJS(mapped, { include: "c" });
equal(result.a, "a");
equal(result.c, 1);
equal(result.d, undefined);
});
test('ko.mapping.toJS should by default ignore the mapping property', function() {
var data = {
a: "a",
b: "b"
};
var fromJS = ko.mapping.fromJS(data);
var result = ko.mapping.toJS(fromJS);
equal(result.a, "a");
equal(result.b, "b");
equal(result.__ko_mapping__, undefined);
});
test('ko.mapping.toJS should by default include the _destroy property', function() {
var data = {
a: "a"
};
var fromJS = ko.mapping.fromJS(data);
fromJS._destroy = true;
var result = ko.mapping.toJS(fromJS);
equal(result.a, "a");
equal(result._destroy, true);
});
test('ko.mapping.toJS should merge the default includes', function() {
var data = {
a: "a"
};
var fromJS = ko.mapping.fromJS(data);
fromJS.b = "b";
fromJS._destroy = true;
var result = ko.mapping.toJS(fromJS, { include: "b" });
equal(result.a, "a");
equal(result.b, "b");
equal(result._destroy, true);
});
test('ko.mapping.toJS should merge the default ignores', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
ko.mapping.defaultOptions().ignore = ["a"];
var fromJS = ko.mapping.fromJS(data);
var result = ko.mapping.toJS(fromJS, { ignore: "b" });
equal(result.a, undefined);
equal(result.b, undefined);
equal(result.c, "c");
});
test('ko.mapping.defaultOptions should by default include the _destroy property', function() {
notEqual(ko.utils.arrayIndexOf(ko.mapping.defaultOptions().include, "_destroy"), -1);
});
test('ko.mapping.defaultOptions.include should be an array', function() {
var didThrow = false;
try {
ko.mapping.defaultOptions().include = {};
ko.mapping.toJS({});
}
catch (ex) {
didThrow = true
}
equal(didThrow, true);
});
test('ko.mapping.defaultOptions.ignore should be an array', function() {
var didThrow = false;
try {
ko.mapping.defaultOptions().ignore = {};
ko.mapping.toJS({});
}
catch (ex) {
didThrow = true
}
equal(didThrow, true);
});
test('ko.mapping.defaultOptions can be set', function() {
var oldOptions = ko.mapping.defaultOptions();
ko.mapping.defaultOptions({ a: "a" });
var newOptions = ko.mapping.defaultOptions();
ko.mapping.defaultOptions(oldOptions);
equal(newOptions.a, "a");
});
test('recognized root-level options should be moved into a root namespace, leaving other options in place', function() {
var recognizedRootProperties = ['create', 'update', 'key', 'arrayChanged'];
// Zero out the default options so they don't interfere with this test
ko.mapping.defaultOptions({});
// Set up a mapping with root and child mappings
var mapping = {
ignore: ['a'],
copy: ['b'],
include: ['c'],
create: function(opts) { return opts.data; },
update: function(opts) { return opts.data; },
key: function(item) { return ko.utils.unwrapObservable(item.id); },
arrayChanged: function(event, item) { },
children: {
ignore: ['a1'],
copy: ['b1'],
include: ['c1'],
create: function(opts) { return opts.data; },
update: function(opts) { return opts.data; },
key: function(item) { return ko.utils.unwrapObservable(item.id); },
arrayChanged: function(event, item) { }
}
};
// Run the mapping through ko.mapping.fromJS
var resultantMapping = ko.mapping.fromJS({}, mapping).__ko_mapping__;
// Test that the recognized root-level mappings were moved into a root-level namespace
for(var i=recognizedRootProperties.length-1; i>=0; i--) {
notDeepEqual(resultantMapping[recognizedRootProperties[i]], mapping[[recognizedRootProperties[i]]]);
deepEqual(resultantMapping[''][recognizedRootProperties[i]], mapping[[recognizedRootProperties[i]]]);
};
// Test that the non-recognized root-level and descendant mappings were left in place
for(property in mapping) {
window[recognizedRootProperties.indexOf(property) == -1 ? 'deepEqual' : 'notDeepEqual'](resultantMapping[property], mapping[property]);
};
});
test('ko.mapping.toJS should ignore properties that were not part of the original model', function () {
var data = {
a: 123,
b: {
b1: 456,
b2: [
"b21", "b22"
],
}
};
var mapped = ko.mapping.fromJS(data);
mapped.extraProperty = ko.observable(333);
mapped.extraFunction = function() {};
var unmapped = ko.mapping.toJS(mapped);
equal(unmapped.a, 123);
equal(unmapped.b.b1, 456);
equal(unmapped.b.b2[0], "b21");
equal(unmapped.b.b2[1], "b22");
equal(unmapped.extraProperty, undefined);
equal(unmapped.extraFunction, undefined);
equal(unmapped.__ko_mapping__, undefined);
});
test('ko.mapping.toJS should ignore properties that were not part of the original model when there are no nested create callbacks', function () {
var data = [
{
a: [{ id: "a1.1" }, { id: "a1.2" }]
}
];
var mapped = ko.mapping.fromJS(data, {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
});
mapped.extraProperty = ko.observable(333);
mapped.extraFunction = function() {};
var unmapped = ko.mapping.toJS(mapped);
equal(unmapped[0].a[0].id, "a1.1");
equal(unmapped[0].a[1].id, "a1.2");
equal(unmapped.extraProperty, undefined);
equal(unmapped.extraFunction, undefined);
equal(unmapped.__ko_mapping__, undefined);
});
test('ko.mapping.toJS should ignore properties that were not part of the original model when there are nested create callbacks', function () {
var data = [
{
a: [{ id: "a1.1" }, { id: "a1.2" }]
}
];
var nestedMappingOptions = {
a: {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
}
};
var mapped = ko.mapping.fromJS(data, {
create: function(options) {
return ko.mapping.fromJS(options.data, nestedMappingOptions);
}
});
mapped.extraProperty = ko.observable(333);
mapped.extraFunction = function() {};
var unmapped = ko.mapping.toJS(mapped);
equal(unmapped[0].a[0].id, "a1.1");
equal(unmapped[0].a[1].id, "a1.2");
equal(unmapped.extraProperty, undefined);
equal(unmapped.extraFunction, undefined);
equal(unmapped.__ko_mapping__, undefined);
});
test('ko.mapping.toJS should ignore specified properties', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
var result = ko.mapping.toJS(data, { ignore: ["b", "c"] });
equal(result.a, "a");
equal(result.b, undefined);
equal(result.c, undefined);
});
test('ko.mapping.toJSON should ignore specified properties', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
var result = ko.mapping.toJSON(data, { ignore: ["b", "c"] });
equal(result, "{\"a\":\"a\"}");
});
test('ko.mapping.toJSON should unwrap everything and then stringify', function () {
var data = ko.observableArray(['a', 1,
{
someProp: ko.observable('Hey')
}]);
var result = ko.mapping.toJSON(data);
// Check via parsing so the specs are independent of browser-specific JSON string formatting
equal(typeof result, 'string');
var parsedResult = ko.utils.parseJson(result);
equal(parsedResult.length, 3);
equal(parsedResult[0], 'a');
equal(parsedResult[1], 1);
equal(parsedResult[2].someProp, 'Hey');
});
test('ko.mapping.fromJS should require a parameter', function () {
var didThrow = false;
try {
ko.mapping.fromJS()
}
catch (ex) {
didThrow = true
}
equal(didThrow, true);
});
test('ko.mapping.fromJS should return an observable if you supply an atomic value', function () {
var atomicValues = ["hello", 123, true, null, undefined];
for (var i = 0; i < atomicValues.length; i++) {
var result = ko.mapping.fromJS(atomicValues[i]);
equal(ko.isObservable(result), true);
equal(result(), atomicValues[i]);
}
});
test('ko.mapping.fromJS should be able to map into an existing object', function () {
var existingObj = {
a: "a"
};
var obj = {
b: "b"
};
ko.mapping.fromJS(obj, {}, existingObj);
equal(ko.isObservable(existingObj.a), false);
equal(ko.isObservable(existingObj.b), true);
equal(existingObj.a, "a");
equal(existingObj.b(), "b");
});
test('ko.mapping.fromJS should return an observableArray if you supply an array, but should not wrap its entries in further observables', function () {
var sampleArray = ["a", "b"];
var result = ko.mapping.fromJS(sampleArray);
equal(typeof result.destroyAll, 'function'); // Just an example of a function on ko.observableArray but not on Array
equal(result().length, 2);
equal(result()[0], "a");
equal(result()[1], "b");
});
test('ko.mapping.fromJS should return an observableArray if you supply an array, and leave entries as observables if there is a create mapping that does that', function () {
var sampleArray = {array: ["a", "b"]};
var result = ko.mapping.fromJS(sampleArray, {
array: {
create: function(options) {
return new ko.observable(options.data);
}
}
});
equal(result.array().length, 2);
equal(ko.isObservable(result.array()[0]),true);
equal(ko.isObservable(result.array()[1]),true);
equal(result.array()[0](), "a");
equal(result.array()[1](), "b");
});
test('ko.mapping.fromJS should not return an observable if you supply an object that could have properties', function () {
equal(ko.isObservable(ko.mapping.fromJS({})), false);
});
test('ko.mapping.fromJS should not wrap functions in an observable', function () {
var result = ko.mapping.fromJS({}, {
create: function(model) {
return {
myFunc: function() {
return 123;
}
}
}
});
equal(result.myFunc(), 123);
});
test('ko.mapping.fromJS update callbacks should pass in a non-observable', function () {
var result = ko.mapping.fromJS({
obj: { a: "a" }
}, {
obj: {
update: function(options) {
equal(options.observable, undefined);
return { b: "b" };
}
}
});
equal(result.obj.b, "b");
});
test('ko.mapping.fromJS update callbacks should pass in an observable, when original is also observable', function () {
var result = ko.mapping.fromJS({
obj: ko.observable("a")
}, {
obj: {
update: function(options) {
return options.observable() + "ab";
}
}
});
equal(result.obj(), "aab");
});
test('ko.mapping.fromJS update callbacks should pass in an observable, when original is not observable', function () {
var result = ko.mapping.fromJS({
obj: "a"
}, {
obj: {
update: function(options) {
return options.observable() + "ab";
}
}
});
equal(result.obj(), "aab");
});
test('ko.mapping.fromJS should map the top-level atomic properties on the supplied object as observables', function () {
var result = ko.mapping.fromJS({
a: 123,
b: 'Hello',
c: true
});
equal(ko.isObservable(result.a), true);
equal(ko.isObservable(result.b), true);
equal(ko.isObservable(result.c), true);
equal(result.a(), 123);
equal(result.b(), 'Hello');
equal(result.c(), true);
});
test('ko.mapping.fromJS should not map the top-level non-atomic properties on the supplied object as observables', function () {
var result = ko.mapping.fromJS({
a: {
a1: "Hello"
}
});
equal(ko.isObservable(result.a), false);
equal(ko.isObservable(result.a.a1), true);
equal(result.a.a1(), 'Hello');
});
test('ko.mapping.fromJS should not map the top-level non-atomic properties on the supplied overriden model as observables', function () {
var result = ko.mapping.fromJS({
a: {
a2: "a2"
}
}, {
create: function(model) {
return {
a: {
a1: "a1"
}
};
}
});
equal(ko.isObservable(result.a), false);
equal(ko.isObservable(result.a.a1), false);
equal(result.a.a2, undefined);
equal(result.a.a1, 'a1');
});
test('ko.mapping.fromJS should not map top-level objects on the supplied overriden model as observables', function () {
var dummyObject = function (options) {
this.a1 = options.a1;
return this;
}
var result = ko.mapping.fromJS({}, {
create: function(model) {
return {
a: new dummyObject({
a1: "Hello"
})
};
}
});
equal(ko.isObservable(result.a), false);
equal(ko.isObservable(result.a.a1), false);
equal(result.a.a1, 'Hello');
});
test('ko.mapping.fromJS should allow non-unique atomic properties', function () {
var vm = ko.mapping.fromJS({
a: [1, 2, 1]
});
deepEqual(vm.a(), [1, 2, 1]);
});
/* speed optimizations don't allow this anymore...
test('ko.mapping.fromJS should not allow non-unique non-atomic properties', function () {
var options = {
key: function(item) { return ko.utils.unwrapObservable(item.id); }
};
var didThrow = false;
try {
ko.mapping.fromJS([
{ id: "a1" },
{ id: "a2" },
{ id: "a1" }
], options);
}
catch (ex) {
didThrow = true
}
equal(didThrow, true);
});
*/
test('ko.mapping.fromJS should map descendant properties on the supplied object as observables', function () {
var result = ko.mapping.fromJS({
a: {
a1: 'a1value',
a2: {
a21: 'a21value',
a22: 'a22value'
}
},
b: {
b1: null,
b2: undefined
}
});
equal(result.a.a1(), 'a1value');
equal(result.a.a2.a21(), 'a21value');
equal(result.a.a2.a22(), 'a22value');
equal(result.b.b1(), null);
equal(result.b.b2(), undefined);
});
test('ko.mapping.fromJS should map observable properties, but without adding a further observable wrapper', function () {
var result = ko.mapping.fromJS({
a: ko.observable('Hey')
});
equal(result.a(), 'Hey');
});
test('ko.mapping.fromJS should escape from reference cycles', function () {
var obj = {};
obj.someProp = {
owner: obj
};
var result = ko.mapping.fromJS(obj);
equal(result.someProp.owner === result, true);
});
test('ko.mapping.fromJS should send relevant create callbacks', function () {
var items = [];
var index = 0;
var result = ko.mapping.fromJS({
a: "hello"
}, {
create: function (model) {
index++;
return model;
}
});
equal(index, 1);
});
test('ko.mapping.fromJS should send relevant create callbacks when mapping arrays', function () {
var items = [];
var index = 0;
var result = ko.mapping.fromJS([
"hello"
], {
create: function (model) {
index++;
return model;
}
});
equal(index, 1);
});
test('ko.mapping.fromJS should send parent along to create callback when creating an object', function() {
var obj = {
a: "a",
b: {
b1: "b1"
}
};
var result = ko.mapping.fromJS(obj, {
"b": {
create: function(options) {
equal(ko.isObservable(options.parent.a), true);
equal(options.parent.a(), "a");
}
}
});
});
test('ko.mapping.fromJS should send parent along to create callback when creating an array item inside an object', function() {
var obj = {
a: "a",
b: [
{ id: 1 },
{ id: 2 }
]
};
var target = {};
var numCreated = 0;
var result = ko.mapping.fromJS(obj, {
"b": {
create: function(options) {
equal(ko.isObservable(options.parent), false);
equal(options.parent, target);
numCreated++;
}
}
}, target);
equal(numCreated, 2);
});
test('ko.mapping.fromJS should send parent along to create callback when creating an array item inside an array', function() {
// parent is the array
var obj = [
{ id: 1 },
{ id: 2 }
];
var target = [];
var numCreated = 0;
var result = ko.mapping.fromJS(obj, {
create: function(options) {
equal(ko.isObservable(options.parent), true);
numCreated++;
}
}, target);
equal(numCreated, 2);
});
test('ko.mapping.fromJS should update objects in arrays that were specified in the overriden model in the create callback', function () {
var options = {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
}
var result = ko.mapping.fromJS([], options);
ko.mapping.fromJS([{
a: "a",
b: "b"
}], {}, result);
equal(ko.isObservable(result), true);
equal(ko.isObservable(result()[0].a), true);
equal(result()[0].a(), "a");
equal(ko.isObservable(result()[0].b), true);
equal(result()[0].b(), "b");
});
test('ko.mapping.fromJS should use the create callback to update objects in arrays', function () {
var created = [];
var arrayEvents = 0;
var options = {
key: function(item) { return ko.utils.unwrapObservable(item.id); },
create: function(options) {
created.push(options.data.id);
return ko.mapping.fromJS(options.data);
},
arrayChanged: function(event, item) {
arrayEvents++;
}
}
var result = ko.mapping.fromJS([
{ id: "a" }
], options);
ko.mapping.fromJS([
{ id: "a" },
{ id: "b" }
], {}, result);
equal(created[0], "a");
equal(created[1], "b");
equal(result()[0].id(), "a");
equal(result()[1].id(), "b");
equal(arrayEvents, 3); // added, retained, added
});
test('ko.mapping.fromJS should not call the create callback for existing objects', function () {
var numCreate = 0;
var options = {
create: function (model) {
numCreate++;
var overridenModel = {};
return overridenModel;
}
};
var items = [];
var index = 0;
var result = ko.mapping.fromJS({
a: "hello"
}, options);
ko.mapping.fromJS({
a: "bye"
}, {}, result);
equal(numCreate, 1);
});
test('ko.mapping.fromJS should not overwrite the existing observable array', function () {
var result = ko.mapping.fromJS({
a: [1]
});
var resultA = result.a;
ko.mapping.fromJS({
a: [1]
}, result);
equal(resultA, result.a);
});
test('ko.mapping.fromJS should send an added callback for every array item that is added to a previously non-existent array', function () {
var added = [];
var options = {
"a" : {
arrayChanged: function (event, newValue) {
if (event === "added") added.push(newValue);
}
}
};
var result = ko.mapping.fromJS({}, options);
ko.mapping.fromJS({
a: [1, 2]
}, {}, result);
equal(added.length, 2);
equal(added[0], 1);
equal(added[1], 2);
});
test('ko.mapping.fromJS should send an added callback for every array item that is added to a previously empty array', function () {
var added = [];
var options = {
"a": {
arrayChanged: function (event, newValue) {
if (event === "added") added.push(newValue);
}
}
};
var result = ko.mapping.fromJS({ a: [] }, options);
ko.mapping.fromJS({
a: [1, 2]
}, {}, result);
equal(added.length, 2);
equal(added[0], 1);
equal(added[1], 2);
});
test('ko.mapping.fromJS should not make observable anything that is not in the js object', function () {
var result = ko.mapping.fromJS({});
result.a = "a";
equal(ko.isObservable(result.a), false);
ko.mapping.fromJS({
b: "b"
}, {}, result);
equal(ko.isObservable(result.a), false);
equal(ko.isObservable(result.b), true);
equal(result.a, "a");
equal(result.b(), "b");
});
test('ko.mapping.fromJS should not make observable anything that is not in the js object when overriding the model', function () {
var options = {
create: function(model) {
return {
a: "a"
}
}
};
var result = ko.mapping.fromJS({}, options);
ko.mapping.fromJS({
b: "b"
}, {}, result);
equal(ko.isObservable(result.a), false);
equal(ko.isObservable(result.b), true);
equal(result.a, "a");
equal(result.b(), "b");
});
test('ko.mapping.fromJS should send an added callback for every array item that is added', function () {
var added = [];
var options = {
"a": {
arrayChanged: function (event, newValue) {
if (event === "added") added.push(newValue);
}
}
};
var result = ko.mapping.fromJS({
a: []
}, options);
ko.mapping.fromJS({
a: [1, 2]
}, {}, result);
equal(added.length, 2);
equal(added[0], 1);
equal(added[1], 2);
});
test('ko.mapping.fromJS should send an added callback for every array item that is added', function () {
var added = [];
var result = ko.mapping.fromJS({
a: [1, 2]
}, {
"a": {
arrayChanged: function (event, newValue) {
if (event === "added") added.push(newValue);
}
}
});
equal(added.length, 2);
equal(added[0], 1);
equal(added[1], 2);
});
test('ko.mapping.fromJSON should parse and then map in the same way', function () {
var jsonString = ko.utils.stringifyJson({ // Note that "undefined" property values are omitted by the stringifier, so not testing those
a: {
a1: 'a1value',
a2: {
a21: 'a21value',
a22: 'a22value'
}
},
b: {
b1: null
}
});
var result = ko.mapping.fromJSON(jsonString);
equal(result.a.a1(), 'a1value');
equal(result.a.a2.a21(), 'a21value');
equal(result.a.a2.a22(), 'a22value');
equal(result.b.b1(), null);
});
test('ko.mapping.fromJS should be able to map empty object structures', function () {
var obj = {
someProp: undefined,
a: []
};
var result = ko.mapping.fromJS(obj);
equal(ko.isObservable(result.someProp), true);
equal(ko.isObservable(result.a), true);
equal(ko.isObservable(result.unknownProperty), false);
});
test('ko.mapping.fromJS should send create callbacks when atomic items are constructed', function () {
var atomicValues = ["hello", 123, true, null, undefined];
var callbacksReceived = 0;
for (var i = 0; i < atomicValues.length; i++) {
var result = ko.mapping.fromJS(atomicValues[i], {
create: function (item) {
callbacksReceived++;
return item;
}
});
}
equal(callbacksReceived, 5);
});
test('ko.mapping.fromJS should send callbacks when atomic array elements are constructed', function () {
var oldItems = {
array: []
};
var newItems = {
array: [{
id: 1
},
{
id: 2
}]
};
var items = [];
var result = ko.mapping.fromJS(oldItems, {
"array": {
arrayChanged: function (event, item) {
if (event == "added")
items.push(item);
}
}
});
ko.mapping.fromJS(newItems, {}, result);
equal(items.length, 2);
});
test('ko.mapping.fromJS should not send callbacks containing parent names when descendant objects are constructed', function () {
var obj = {
a: {
a1: "hello",
a2: 234,
a3: {
a31: null
}
}
};
var parents = [];
var pushParent = function (item, parent) {
parents.push(parent);
return item;
};
var result = ko.mapping.fromJS(obj, {
create: pushParent
});
equal(parents.length, 1);
equal(parents[0], undefined);
});
test('ko.mapping.fromJS should create instead of update, on empty objects', function () {
var obj = {
a: ["a1", "a2"]
};
var result;
result = ko.mapping.fromJS({});
ko.mapping.fromJS(obj, {}, result);
equal(result.a().length, 2);
equal(result.a()[0], "a1");
equal(result.a()[1], "a2");
});
test('ko.mapping.fromJS should update atomic observables', function () {
var atomicValues = ["hello", 123, true, null, undefined];
var atomicValues2 = ["hello2", 124, false, "not null", "defined"];
for (var i = 0; i < atomicValues.length; i++) {
var result = ko.mapping.fromJS(atomicValues[i]);
ko.mapping.fromJS(atomicValues2[i], {}, result);
equal(ko.isObservable(result), true);
equal(result(), atomicValues2[i]);
}
});
test('ko.mapping.fromJS should update objects', function () {
var obj = {
a: "prop",
b: {
b1: null,
b2: "b2"
}
}
var obj2 = {
a: "prop2",
b: {
b1: 124,
b2: "b22"
}
}
var result = ko.mapping.fromJS(obj);
ko.mapping.fromJS(obj2, {}, result);
equal(result.a(), "prop2");
equal(result.b.b1(), 124);
equal(result.b.b2(), "b22");
});
test('ko.mapping.fromJS should update initially empty objects', function () {
var obj = {
a: undefined,
b: []
}
var obj2 = {
a: "prop2",
b: ["b1", "b2"]
}
var result = ko.mapping.fromJS(obj);
ko.mapping.fromJS(obj2, {}, result);
equal(result.a(), "prop2");
equal(result.b()[0], "b1");
equal(result.b()[1], "b2");
});
test('ko.mapping.fromJS should update arrays containing atomic types', function () {
var obj = ["a1", "a2", 6];
var obj2 = ["a3", "a4", 7];
var result = ko.mapping.fromJS(obj);
ko.mapping.fromJS(obj2, {}, result);
equal(result().length, 3);
equal(result()[0], "a3");
equal(result()[1], "a4");
equal(result()[2], 7);
});
test('ko.mapping.fromJS should update arrays containing objects', function () {
var obj = {
a: [{
id: 1,
value: "a1"
},
{
id: 2,
value: "a2"
}]
}
var obj2 = {
a: [{
id: 1,
value: "a1"
},
{
id: 3,
value: "a3"
}]
}
var options = {
"a": {
key: function (item) {
return item.id;
}
}
};
var result = ko.mapping.fromJS(obj, options);
ko.mapping.fromJS(obj2, {}, result);
equal(result.a().length, 2);
equal(result.a()[0].value(), "a1");
equal(result.a()[1].value(), "a3");
});
test('ko.mapping.fromJS should send a callback when adding new objects to an array', function () {
var obj = [{
id: 1
}];
var obj2 = [{
id: 1
},
{
id: 2
}];
var mappedItems = [];
var options = {
key: function(item) {
return item.id;
},
arrayChanged: function (event, item) {
if (event == "added") mappedItems.push(item);
}
};
var result = ko.mapping.fromJS(obj, options);
ko.mapping.fromJS(obj2, {}, result);
equal(mappedItems.length, 2);
equal(mappedItems[0].id(), 1);
equal(mappedItems[1].id(), 2);
});
test('ko.mapping.fromJS should be able to update from an observable source', function () {
var obj = [{
id: 1
}];
var obj2 = ko.mapping.fromJS([{
id: 1
},
{
id: 2
}]);
var result = ko.mapping.fromJS(obj);
ko.mapping.fromJS(obj2, {}, result);
equal(result().length, 2);
equal(result()[0].id(), 1);
equal(result()[1].id(), 2);
});
test('ko.mapping.fromJS should send a deleted callback when an item was deleted from an array', function () {
var obj = [1, 2];
var obj2 = [1];
var items = [];
var options = {
arrayChanged: function (event, item) {
if (event == "deleted") items.push(item);
}
};
var result = ko.mapping.fromJS(obj, options);
ko.mapping.fromJS(obj2, {}, result);
equal(items.length, 1);
equal(items[0], 2);
});
test('ko.mapping.fromJS should reuse options that were added in ko.mapping.fromJS', function() {
var viewModelMapping = {
key: function(data) {
return ko.utils.unwrapObservable(data.id);
},
create: function(options) {
return new viewModel(options);
}
};
var viewModel = function(options) {
var mapping = {
entries: viewModelMapping
};
ko.mapping.fromJS(options.data, mapping, this);
this.func = function() { return true; };
};
var model = ko.mapping.fromJS([], viewModelMapping);
var data = [{
"id": 1,
"entries": [{
"id": 2,
"entries": [{
"id": 3,
"entries": []
}]
}]
}];
ko.mapping.fromJS(data, {}, model);
ko.mapping.fromJS(data, {}, model);
equal(model()[0].func(), true);
equal(model()[0].entries()[0].func(), true);
equal(model()[0].entries()[0].entries()[0].func(), true);
});
test('ko.mapping.toJS should not change the mapped object', function() {
var obj = {
a: "a"
}
var result = ko.mapping.fromJS(obj);
result.b = ko.observable(123);
var toJS = ko.mapping.toJS(result);
equal(ko.isObservable(result.b), true);
equal(result.b(), 123);
equal(toJS.b, undefined);
});
test('ko.mapping.toJS should not change the mapped array', function() {
var obj = [{
a: 50
}]
var result = ko.mapping.fromJS(obj);
result()[0].b = ko.observable(123);
var toJS = ko.mapping.toJS(result);
equal(ko.isObservable(result()[0].b), true);
equal(result()[0].b(), 123);
});
test('observableArray.mappedRemove should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedRemove({ id : 2 });
equal(result().length, 1);
});
test('observableArray.mappedRemove with predicate should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedRemove(function(key) {
return key == 2;
});
equal(result().length, 1);
});
test('observableArray.mappedRemoveAll should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedRemoveAll([{ id : 2 }]);
equal(result().length, 1);
});
test('observableArray.mappedDestroy should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedDestroy({ id : 2 });
equal(result()[0]._destroy, undefined);
equal(result()[1]._destroy, true);
});
test('observableArray.mappedDestroy with predicate should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedDestroy(function(key) {
return key == 2;
});
equal(result()[0]._destroy, undefined);
equal(result()[1]._destroy, true);
});
test('observableArray.mappedDestroyAll should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
result.mappedDestroyAll([{ id : 2 }]);
equal(result()[0]._destroy, undefined);
equal(result()[1]._destroy, true);
});
test('observableArray.mappedIndexOf should use key callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
equal(result.mappedIndexOf({ id : 1 }), 0);
equal(result.mappedIndexOf({ id : 2 }), 1);
equal(result.mappedIndexOf({ id : 3 }), -1);
});
test('observableArray.mappedCreate should use key callback if available and not allow duplicates', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
});
var caught = false;
try {
result.mappedCreate({ id : 1 });
}
catch(e) {
caught = true;
}
equal(caught, true);
equal(result().length, 2);
});
test('observableArray.mappedCreate should use create callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var childModel = function(data){
ko.mapping.fromJS(data, {}, this);
this.Hello = ko.observable("hello");
}
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
},
create: function(options){
return new childModel(options.data);
}
});
result.mappedCreate({ id: 3 });
var index = result.mappedIndexOf({ id : 3 });
equal(index, 2);
equal(result()[index].Hello(), "hello");
});
test('observableArray.mappedCreate should use update callback if available', function() {
var obj = [
{ id : 1 },
{ id : 2 }
]
var childModel = function(data){
ko.mapping.fromJS(data, {}, this);
}
var result = ko.mapping.fromJS(obj, {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
},
create: function(options){
return new childModel(options.data);
},
update: function(options){
return {
bla: options.data.id * 10
};
}
});
result.mappedCreate({ id: 3 });
equal(result()[0].bla, 10);
equal(result()[2].bla, 30);
});
test('ko.mapping.fromJS should merge options from subsequent calls', function() {
var obj = ['a'];
var result = ko.mapping.fromJS(obj, { dummyOption1: 1 });
ko.mapping.fromJS({}, { dummyOption2: 2 }, result);
equal(result.__ko_mapping__.dummyOption1, 1);
equal(result.__ko_mapping__.dummyOption2, 2);
});
test('ko.mapping.fromJS should correctly handle falsey values', function () {
var obj = [false, ""];
var result = ko.mapping.fromJS(obj);
equal(result()[0] === false, true);
equal(result()[1] === "", true);
});
test('ko.mapping.fromJS should correctly handle falsey values in keys', function () {
var created = [];
var gotDeletedEvent = false;
var options = {
key: function(item) { return ko.utils.unwrapObservable(item.id); },
arrayChanged: function(event, item) {
if (event === "deleted") gotDeletedEvent = true;
}
}
var result = ko.mapping.fromJS([
{ id: 0 }
], options);
ko.mapping.fromJS([
{ id: 0 },
{ id: 1 }
], {}, result);
equal(gotDeletedEvent, false);
});
test('ko.mapping.fromJS should allow duplicate atomic items in arrays', function () {
var result = ko.mapping.fromJS([
"1", "1", "2"
]);
equal(result().length, 3);
equal(result()[0], "1");
equal(result()[1], "1");
equal(result()[2], "2");
ko.mapping.fromJS([
"1", "1", "1", "2"
], {}, result);
equal(result().length, 4);
equal(result()[0], "1");
equal(result()[1], "1");
equal(result()[2], "1");
equal(result()[3], "2");
});
test('when doing ko.mapping.fromJS on an already mapped object, the new options should combine with the old', function() {
var dataA = {
a: "a"
};
var dataB = {
b: "b"
};
var mapped = {};
ko.mapping.fromJS(dataA, {}, mapped);
ko.mapping.fromJS(dataB, {}, mapped);
equal(mapped.__ko_mapping__.mappedProperties.a, true);
equal(mapped.__ko_mapping__.mappedProperties.b, true);
});
test('ko.mapping.fromJS should merge options from subsequent calls', function() {
var obj = ['a'];
var result = ko.mapping.fromJS(obj, { dummyOption1: 1 });
ko.mapping.fromJS(['b'], { dummyOption2: 2 }, result);
equal(result.__ko_mapping__.dummyOption1, 1);
equal(result.__ko_mapping__.dummyOption2, 2);
});
test('ko.mapping.fromJS should work on unmapped objects', function() {
var obj = ko.observableArray(['a']);
ko.mapping.fromJS(['b'], {}, obj);
equal(obj()[0], 'b');
});
test('ko.mapping.fromJS should update an array only once', function() {
var obj = {
a: ko.observableArray()
};
var updateCount = 0;
obj.a.subscribe(function() {
updateCount++;
});
ko.mapping.fromJS({ a: [1, 2, 3] }, {}, obj);
equal(updateCount, 1);
});
test('ko.mapping.fromJSON should merge options from subsequent calls', function() {
var obj = ['a'];
var result = ko.mapping.fromJS(obj, { dummyOption1: 1 });
ko.mapping.fromJSON('["b"]', { dummyOption2: 2 }, result);
equal(result.__ko_mapping__.dummyOption1, 1);
equal(result.__ko_mapping__.dummyOption2, 2);
});
test('ko.mapping.fromJS should be able to update observables not created by fromJS', function() {
var existing = {
a: ko.observable(),
d: ko.observableArray()
};
ko.mapping.fromJS({
a: {
b: "b!"
},
d: [2]
}, {}, existing);
equal(existing.a().b(), "b!");
equal(existing.d().length, 1);
equal(existing.d()[0], 2);
});
test('ko.mapping.fromJS should accept an already mapped object as the second parameter', function() {
var mapped = ko.mapping.fromJS({ a: "a" });
ko.mapping.fromJS({ a: "b" }, mapped);
equal(mapped.a(), "b");
});
test('ko.mapping.fromJS should properly map objects that appear in multiple places', function() {
var obj = { title: "Lorem ipsum" }, obj2 = { title: "Lorem ipsum 2" };
var x = [obj,obj2];
var y = { o: obj, x: x };
var z = ko.mapping.fromJS(y);
equal(y.x[0].title, "Lorem ipsum");
equal(z.x()[0].title(), "Lorem ipsum");
});
test('ko.mapping.fromJS should properly update arrays containing a NULL key', function() {
var data = [1,2,3,null];
var model=ko.mapping.fromJS(data);
deepEqual(model(), [1,2,3,null]);
data = [null,1,2,3];
ko.mapping.fromJS(data, {}, model);
deepEqual(model(), [null,1,2,3]);
});
test('ko.mapping.visitModel will pass in correct parent names', function() {
var data = { a: { a2: "a2value" } };
var parents = [];
ko.mapping.visitModel(data, function(obj, parent) {
parents.push(parent);
});
equal(parents.length, 3);
equal(parents[0], undefined);
equal(parents[1], "a");
equal(parents[2], "a.a2");
});
test('ko.mapping.toJS should merge the default observe', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
ko.mapping.defaultOptions().observe = ["a"];
var result = ko.mapping.fromJS(data, { observe: "b" });
equal(ko.isObservable(result.a), true);
equal(ko.isObservable(result.b), true);
equal(ko.isObservable(result.c), false);
});
test('ko.mapping.fromJS should observe specified single property', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { observe: "a" });
equal(result.a(), "a");
equal(result.b, "b");
});
test('ko.mapping.fromJS should observe specified array', function() {
var data = {
a: "a",
b: ["b1", "b2"]
};
var result = ko.mapping.fromJS(data, { observe: "b" });
equal(result.a, "a");
equal(ko.isObservable(result.b), true);
});
test('ko.mapping.fromJS should observe specified array item', function() {
var data = {
a: "a",
b: [{ b1: "v1" }, { b2: "v2" }]
};
var result = ko.mapping.fromJS(data, { observe: "b[0].b1" });
equal(result.a, "a");
equal(result.b[0].b1(), "v1");
equal(result.b[1].b2, "v2");
});
test('ko.mapping.fromJS should observe specified array but not the children', function() {
var data = {
a: "a",
b: [{ b1: "v1" }, { b2: "v2" }]
};
var result = ko.mapping.fromJS(data, { observe: "b" });
equal(result.a, "a");
equal(result.b()[0].b1, "v1");
equal(result.b()[1].b2, "v2");
});
test('ko.mapping.fromJS should observe specified single property, also when going back .toJS', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { observe: "b" });
var js = ko.mapping.toJS(result);
equal(js.a, "a");
equal(js.b, "b");
});
test('ko.mapping.fromJS should copy specified single property, also when going back .toJS, except when overridden', function() {
var data = {
a: "a",
b: "b"
};
var result = ko.mapping.fromJS(data, { observe: "b" });
var js = ko.mapping.toJS(result, { ignore: "b" });
equal(js.a, "a");
equal(js.b, undefined);
});
test('ko.mapping.fromJS explicit declared none observable members should not be mapped to an observable', function() {
var data = {
a: "a",
b: "b",
c: "c"
};
var ViewModel = function() {
this.a = ko.observable();
this.b = null;
};
var result = ko.mapping.fromJS(data, {}, new ViewModel());
equal(ko.isObservable(result.a), true);
equal(ko.isObservable(result.b), false);
equal(ko.isObservable(result.c), true);
equal(result.b, data.b);
});
test('ko.mapping.toJS explicit declared none observable members should be mapped toJS correctly', function() {
var data = {
a: "a",
};
var ViewModel = function() {
this.a = null;
};
var result = ko.mapping.fromJS(data, {}, new ViewModel());
var js = ko.mapping.toJS(result);
equal(js.b, data.b);
});