From c1560ab139d37c7e177ef60d0d5665a49192507b Mon Sep 17 00:00:00 2001 From: neverfox Date: Sun, 9 Jun 2013 17:27:27 -0700 Subject: [PATCH 1/2] Add AMD wrapper --- knockout.viewmodel.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/knockout.viewmodel.js b/knockout.viewmodel.js index 91b75ec..4131e3a 100644 --- a/knockout.viewmodel.js +++ b/knockout.viewmodel.js @@ -4,7 +4,16 @@ /*jshint eqnull:true, boss:true, loopfunc:true, evil:true, laxbreak:true, undef:true, unused:true, browser:true, immed:true, devel:true, sub: true, maxerr:50 */ /*global ko:false */ -(function () { +// Uses AMD or browser globals to create a Knockout plugin. +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['knockout'], factory); + } else { + // Browser globals + factory(ko); + } +}(function (ko) { //Module declarations. For increased compression with simple settings on the closure compiler, //the ko functions are stored in variables. These variable names will be shortened by the compiler, //whereas references to ko would not be. There is also a performance savings from this. @@ -491,4 +500,4 @@ return recursiveUpdate(model, viewmodel, rootContext, undefined, noncontiguousObjectUpdateCount); } }; -}()); +})); From 7cbee55316e0e49ac843e5ebabb6419c9f234098 Mon Sep 17 00:00:00 2001 From: neverfox Date: Sun, 9 Jun 2013 17:27:57 -0700 Subject: [PATCH 2/2] Add AMD test suite --- Tests/amd/Tests.htm | 71 ++ Tests/amd/amd-qunit-tests.js | 11 + Tests/amd/array-qunit-tests.js | 42 ++ Tests/amd/fromModel-Basic-qunit-tests.js | 168 +++++ Tests/amd/fromModel-Mapping-qunit-tests.js | 671 ++++++++++++++++++ .../amd/fromModelToModel-Basic-qunit-tests.js | 92 +++ .../fromModelToModel-Mapping-qunit-tests.js | 164 +++++ Tests/amd/issues-qunit-tests.js | 94 +++ Tests/amd/main-qunit-tests.js | 48 ++ Tests/amd/nestedObject-qunit-tests.js | 249 +++++++ Tests/amd/null-Basic-qunit-tests.js | 69 ++ Tests/amd/null-Mapping-qunit-tests.js | 280 ++++++++ Tests/amd/simpleTypes-qunit-tests.js | 218 ++++++ Tests/amd/toModel-Basic-qunit-tests.js | 162 +++++ Tests/amd/undefined-Basic-qunit-tests.js | 69 ++ Tests/amd/undefined-Mapping-qunit-tests.js | 280 ++++++++ .../amd/updateFromModel-Basic-qunit-tests.js | 286 ++++++++ ...romModel-Mapping-contiguous-qunit-tests.js | 54 ++ ...Model-Mapping-noncontiguous-qunit-tests.js | 51 ++ .../updateFromModel-Mapping-qunit-tests.js | 0 20 files changed, 3079 insertions(+) create mode 100644 Tests/amd/Tests.htm create mode 100644 Tests/amd/amd-qunit-tests.js create mode 100644 Tests/amd/array-qunit-tests.js create mode 100644 Tests/amd/fromModel-Basic-qunit-tests.js create mode 100644 Tests/amd/fromModel-Mapping-qunit-tests.js create mode 100644 Tests/amd/fromModelToModel-Basic-qunit-tests.js create mode 100644 Tests/amd/fromModelToModel-Mapping-qunit-tests.js create mode 100644 Tests/amd/issues-qunit-tests.js create mode 100644 Tests/amd/main-qunit-tests.js create mode 100644 Tests/amd/nestedObject-qunit-tests.js create mode 100644 Tests/amd/null-Basic-qunit-tests.js create mode 100644 Tests/amd/null-Mapping-qunit-tests.js create mode 100644 Tests/amd/simpleTypes-qunit-tests.js create mode 100644 Tests/amd/toModel-Basic-qunit-tests.js create mode 100644 Tests/amd/undefined-Basic-qunit-tests.js create mode 100644 Tests/amd/undefined-Mapping-qunit-tests.js create mode 100644 Tests/amd/updateFromModel-Basic-qunit-tests.js create mode 100644 Tests/amd/updateFromModel-Mapping-contiguous-qunit-tests.js create mode 100644 Tests/amd/updateFromModel-Mapping-noncontiguous-qunit-tests.js create mode 100644 Tests/amd/updateFromModel-Mapping-qunit-tests.js diff --git a/Tests/amd/Tests.htm b/Tests/amd/Tests.htm new file mode 100644 index 0000000..4df893c --- /dev/null +++ b/Tests/amd/Tests.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + Knockout Viewmodel Plugin Unit Tests - AMD + + + + + + +
+
+ View on GitHub + +

Knockout Viewmodel Plugin

+

Cleaner, Faster, Better Knockout Mapping

+ +
+ Download this project as a .zip file + Download this project as a tar.gz file +
+
+
+ + +
+
+
Download Latest Version
+ (also on Nuget) +
+
+
+
+ + + + \ No newline at end of file diff --git a/Tests/amd/amd-qunit-tests.js b/Tests/amd/amd-qunit-tests.js new file mode 100644 index 0000000..dad32cf --- /dev/null +++ b/Tests/amd/amd-qunit-tests.js @@ -0,0 +1,11 @@ +define(["knockout", "viewmodel"], function(ko) { + var run = function() { + module("AMD Tests"); + test("Module Loading", function() { + ok(ko, "ko Defined"); + ok(ko.viewmodel, "ko.viewmodel Defined"); + ok(!window.ko, "Global ko Not Defined"); + }); + }; + return {run: run}; +}); diff --git a/Tests/amd/array-qunit-tests.js b/Tests/amd/array-qunit-tests.js new file mode 100644 index 0000000..1b88f9f --- /dev/null +++ b/Tests/amd/array-qunit-tests.js @@ -0,0 +1,42 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model; + module("toModel Basic", { + setup: function () { + //ko.viewmodel.options.logging = true; + model = { + array: [] + }; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + } + }); + + + test("array push pop", function () { + var viewmodel, result; + + viewmodel = ko.viewmodel.fromModel(model); + viewmodel.array.push({ test: true }); + result = viewmodel.array.pop(); + + assert(result.test(), true); + + }); + + test("array push pop", function () { + var viewmodel, result; + + viewmodel = ko.viewmodel.fromModel(model); + viewmodel.array.push({ test: true }, true); + result = viewmodel.array.pop(); + + assert(result.test, true); + + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/fromModel-Basic-qunit-tests.js b/Tests/amd/fromModel-Basic-qunit-tests.js new file mode 100644 index 0000000..ebf3bed --- /dev/null +++ b/Tests/amd/fromModel-Basic-qunit-tests.js @@ -0,0 +1,168 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("fromModel Basic", { + setup: function () { + //ko.viewmodel.options.logging = true; + if(ko.viewmodel.options.hasOwnProperty("makeChildArraysObservable")){ + ko.viewmodel.options.makeChildArraysObservable = true; + } + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + + test("Default simple types", function () { + var model, viewmodel; + + model = { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.stringProp(), model.stringProp, "String Test"); + deepEqual(viewmodel.number(), model.number, "Number Test"); + deepEqual(viewmodel.date(), model.date, "Date Test"); + deepEqual(viewmodel.emptyArray(), model.emptyArray, "Array Test"); + }); + + + test("Default nested object", function () { + var model, viewmodel; + + model = { + nestedObject: { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + } + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.nestedObject.stringProp(), model.nestedObject.stringProp, "String Test"); + deepEqual(viewmodel.nestedObject.number(), model.nestedObject.number, "Number Test"); + deepEqual(viewmodel.nestedObject.date(), model.nestedObject.date, "Date Test"); + deepEqual(viewmodel.nestedObject.emptyArray(), model.nestedObject.emptyArray, "Array Test"); + }); + + + test("Default object array", function () { + var model, viewmodel; + + model = { + objectArray: [ + {} + ] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.objectArray()[0], model.objectArray[0], "Object Test"); + }); + + + test("Default object array simple types", function () { + var model, viewmodel; + + model = { + objectArray: [ + { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + } + ] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.objectArray()[0].stringProp(), model.objectArray[0].stringProp, "String Test"); + deepEqual(viewmodel.objectArray()[0].number(), model.objectArray[0].number, "Number Test"); + deepEqual(viewmodel.objectArray()[0].date(), model.objectArray[0].date, "Date Test"); + deepEqual(viewmodel.objectArray()[0].emptyArray(), model.objectArray[0].emptyArray, "Array Test"); + }); + + + test("makeChildArraysObservable is false Mode Default nested array", function () { + var model, viewmodel; + + model = { + nestedArray: [[]] + }; + + ko.viewmodel.options.makeChildArraysObservable = false; + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(ko.viewmodel.options.makeChildArraysObservable, false); + deepEqual(viewmodel.nestedArray()[0], model.nestedArray[0], "Array Test"); + }); + + test("makeChildArraysObservable is false double nested array", function () { + var model, viewmodel; + + model = { + nestedArray: [[[]]] + }; + + ko.viewmodel.options.makeChildArraysObservable = false; + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(ko.viewmodel.options.makeChildArraysObservable, false); + deepEqual(viewmodel.nestedArray()[0][0], model.nestedArray[0][0], "Array Test"); + }); + + //if undefined then we are using facade around ko.mapping + //used to exclude tests that are incompatable with ko.mapping + if (ko.viewmodel.options.makeChildArraysObservable !== undefined) { + test("makeChildArraysObservable is true Default nested array", function () { + var model, viewmodel; + + model = { + nestedArray: [[]] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(ko.viewmodel.options.makeChildArraysObservable, true); + deepEqual(viewmodel.nestedArray()[0](), model.nestedArray[0], "Array Test"); + }); + + test("makeChildArraysObservable is true double nested array", function () { + var model, viewmodel; + + model = { + nestedArray: [[[]]] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(ko.viewmodel.options.makeChildArraysObservable, true); + deepEqual(viewmodel.nestedArray()[0]()[0](), model.nestedArray[0][0], "Array Test"); + }); + } + + + test("Default string array", function () { + var model, viewmodel; + + model = { + stringArray: ["Test", "Test"] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.stringArray()[0], model.stringArray[0], "String Array Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/fromModel-Mapping-qunit-tests.js b/Tests/amd/fromModel-Mapping-qunit-tests.js new file mode 100644 index 0000000..e391157 --- /dev/null +++ b/Tests/amd/fromModel-Mapping-qunit-tests.js @@ -0,0 +1,671 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("fromModel Mapping", { + setup: function () { + //ko.viewmodel.options.logging = true; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + test("Extend full path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "{root}.test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.test.stringProp.repeat(), viewmodel.test.stringProp() + viewmodel.test.stringProp()); + }); + + test("Extend full path with shared", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + }, + otherTest: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "{root}.test.stringProp": "repeat", + "{root}.otherTest.stringProp": { + map: "repeat" + } + }, + shared: { + "repeat": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.test.stringProp.repeat(), viewmodel.test.stringProp() + viewmodel.test.stringProp()); + deepEqual(viewmodel.otherTest.stringProp.repeat(), viewmodel.otherTest.stringProp() + viewmodel.otherTest.stringProp()); + }); + + test("Extend object property path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.test.stringProp.repeat(), viewmodel.test.stringProp() + viewmodel.test.stringProp()); + }); + + test("Extend property path", function () { + var model, viewmodel, modelResult; + + model = { + stringProp: "test" + }; + + var customMapping = { + extend: { + "stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.stringProp.repeat(), viewmodel.stringProp() + viewmodel.stringProp()); + }); + + test("Extend full path wins over object property path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "{root}.test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + }, + "test.stringProp": function (obj) { + return null; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.test.stringProp.repeat(), viewmodel.test.stringProp() + viewmodel.test.stringProp()); + }); + + test("Extend full path wins over property path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "{root}.test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + }, + "stringProp": function (obj) { + return null; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.test.stringProp.repeat(), viewmodel.test.stringProp() + viewmodel.test.stringProp()); + }); + + + test("Extend array array-item property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "items[i].test": function (obj) { + obj.repeat = ko.computed(function () { + return obj.stringProp() + obj.stringProp(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.repeat(); + expected = model.items[0].test.stringProp + model.items[0].test.stringProp; + + deepEqual(actual, expected); + }); + + test("Extend array array-item property path wins over array-item property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "[i].test": function (obj) { + return null; + }, + "items[i].test": function (obj) { + obj.repeat = ko.computed(function () { + return obj.stringProp() + obj.stringProp(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.repeat(); + expected = model.items[0].test.stringProp + model.items[0].test.stringProp; + + deepEqual(actual, expected); + }); + + test("Extend array array-item property path wins over property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "test": function (obj) { + return null; + }, + "items[i].test": function (obj) { + obj.repeat = ko.computed(function () { + return obj.stringProp() + obj.stringProp(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.repeat(); + expected = model.items[0].test.stringProp + model.items[0].test.stringProp; + + deepEqual(actual, expected); + }); + + test("Extend all array items", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "[i]": function (obj) { + obj.IsNew = false; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].IsNew; + expected = false; + + deepEqual(actual, expected); + }); + + + test("Extended Array Push with Map", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + }, + id: 1259 + }] + }; + + var customMapping = { + extend: { + "{root}.items[i]": { + map: function (obj) { + obj.IsNew = (obj.id() > 0) ? false : true; + }, + unmap: function (obj, vm) { + if (vm) {//not using this param but test will fail if not available, easy way to verify that it is passe in + delete obj.IsNew; + } + return obj; + } + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.items()[0].IsNew, false, "Extend logic - object with id is not new");//1 + + viewmodel.items.pushFromModel({ + test: { + stringProp: "test" + }, + id: null + }); + + deepEqual(viewmodel.items()[1].IsNew, true, "Extend logic applied to pushFromModel - object with id OF null is not new");//2 + + actual = viewmodel.items()[0]; + expected = viewmodel.items.pop() + + notStrictEqual(actual, expected, "Pop does not change object");//3 + + viewmodel.items.push(expected); + + actual = viewmodel.items()[0]; + expected = viewmodel.items.pop() + + notStrictEqual(actual, expected, "Pushing and popping does not change object");//4 + + expected = model.items[0]; + actual = viewmodel.items.popToModel(); + + deepEqual(actual, expected, "popToModel calls unmap and removes IsNew property");//5 + + }); + + test("Extended Array Push without Map", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "[i]": function (obj) { + obj.IsNew = false; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + viewmodel.items.push({ + test: { + stringProp: "test" + } + }, {map:false}); + + notEqual(viewmodel.items.pop(), viewmodel.items()[0]); + }); + + test("Exclude full path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + exclude: ["{root}.items[i].test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + test("Exclude full path wins over append object-property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + exclude: ["{root}.items[i].test"], + append: ["items[i].test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + test("Exclude object-property path wins over append property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + exclude: ["items[i].test"], + append: ["test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + + test("Same path Last in wins", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + exclude: ["items[i].test"], + append: ["items[i].test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + notEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + test("Exclude array item property path", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + exclude: ["items[i].test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + test("Append property", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + append: ["items[i]"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.stringProp; + expected = model.items[0].test.stringProp + + deepEqual(actual, expected, "Item Not Mapped"); + }); + + test("Override array", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + override: ["[i]"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.stringProp(); + expected = model.items[0].test.stringProp + + deepEqual(actual, expected, "Item Not Mapped"); + }); + + test("Override object", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + override: ["{root}"] + }; + + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel.items()[0].test.stringProp(); + expected = model.items[0].test.stringProp + + deepEqual(actual, expected, "Item Not Mapped"); + }); + + test("Custom Success", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + custom: { + "test": function (obj) { + return obj ? true : false; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.items()[0].test, true, "Item Not Mapped"); + }); + + test("Custom Success with shared", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + custom: { + "test": "test" + }, + shared: { + "test": function (obj) { + return obj ? true : false; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.items()[0].test, true, "Item Not Mapped"); + }); + + test("Custom Fail", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + custom: { + "test": function (obj) { + + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + + test("Custom Obsevable", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + custom: { + "test": function (obj) { + + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel.items()[0].test, undefined, "Item Not Mapped"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/fromModelToModel-Basic-qunit-tests.js b/Tests/amd/fromModelToModel-Basic-qunit-tests.js new file mode 100644 index 0000000..92d6c18 --- /dev/null +++ b/Tests/amd/fromModelToModel-Basic-qunit-tests.js @@ -0,0 +1,92 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("fromModel toModel Basic", { + setup: function () { + //ko.viewmodel.options.logging = true; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + test("Default Basic Types", function () { + var model, viewmodel, modelResult; + + model = { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, model.stringProp, "String Test"); + deepEqual(modelResult.number, model.number, "Number Test"); + deepEqual(modelResult.date, model.date, "Date Test"); + deepEqual(modelResult.emptyArray, model.emptyArray, "Array Test"); + }); + + test("Default Nested Object", function () { + var model, viewmodel, modelResult; + + model = { + nestedObject: { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + } + }; + + viewmodel = ko.viewmodel.fromModel(model); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedObject.stringProp, model.nestedObject.stringProp, "String Test"); + deepEqual(modelResult.nestedObject.number, model.nestedObject.number, "Number Test"); + deepEqual(modelResult.nestedObject.date, model.nestedObject.date, "Date Test"); + deepEqual(modelResult.nestedObject.emptyArray, model.nestedObject.emptyArray, "Array Test"); + }); + + test("Default Object Array", function () { + var model, viewmodel, modelResult; + + model = { + objectArray: [ + { + stringProp: "test", + number: 5, + date: new Date("01/01/2001"), + emptyArray: [] + } + ] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.objectArray[0].stringProp, model.objectArray[0].stringProp, "String Test"); + deepEqual(modelResult.objectArray[0].number, model.objectArray[0].number, "Number Test"); + deepEqual(modelResult.objectArray[0].date, model.objectArray[0].date, "Date Test"); + deepEqual(modelResult.objectArray[0].emptyArray, model.objectArray[0].emptyArray, "Array Test"); + }); + + test("Default Nested Array", function () { + var viewmodel, modelResult; + + viewmodel = ko.observable({ + nestedArray: ko.observableArray([ko.observableArray([])]) + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedArray[0], viewmodel().nestedArray()[0](), "Array Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/fromModelToModel-Mapping-qunit-tests.js b/Tests/amd/fromModelToModel-Mapping-qunit-tests.js new file mode 100644 index 0000000..69e7acd --- /dev/null +++ b/Tests/amd/fromModelToModel-Mapping-qunit-tests.js @@ -0,0 +1,164 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("fromModel toModel Mapping", { + setup: function () { + //ko.viewmodel.options.logging = true; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + test("Extend full path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "{root}.test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel().test().stringProp.repeat(), viewmodel().test().stringProp() + viewmodel().test().stringProp(), "Extension Added"); + }); + + test("Extend object property path", function () { + var model, viewmodel, modelResult; + + model = { + test: { + stringProp: "test" + } + }; + + var customMapping = { + extend: { + "test.stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel().test().stringProp.repeat(), viewmodel().test().stringProp() + viewmodel().test().stringProp(), "Extension Added"); + }); + + test("Extend name path", function () { + var model, viewmodel, modelResult; + + model = { + stringProp: "test" + }; + + var customMapping = { + extend: { + "stringProp": function (obj) { + obj.repeat = ko.computed(function () { + return obj() + obj(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + deepEqual(viewmodel().stringProp.repeat(), viewmodel().stringProp() + viewmodel().stringProp(), "Extension Added"); + }); + + + test("Extend array item property", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + extend: { + "items[i].test": function (obj) { + obj.repeat = ko.computed(function () { + return obj().stringProp() + obj().stringProp(); + }); + return obj; + } + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + actual = viewmodel().items()[0]().test.repeat(); + expected = model.items[0].test.stringProp + model.items[0].test.stringProp; + + deepEqual(actual, expected); + }); + + test("Exclude property", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var options = { + exclude: ["items[i].test"] + }; + + viewmodel = ko.viewmodel.fromModel(model, options); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel().items()[0]().test, undefined, "fromModel assert"); + deepEqual(modelResult.items[0].test, undefined, "toModel assert"); + }); + + test("Append property", function () { + var model, viewmodel, modelResult, actual, expected; + + model = { + items: [{ + test: { + stringProp: "test" + } + }] + }; + + var customMapping = { + append: ["items[i]"] + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel().items()[0].test.stringProp, model.items[0].test.stringProp, "fromModel assert"); + deepEqual(modelResult.items[0].test.stringProp, model.items[0].test.stringProp, "toModel assert"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/issues-qunit-tests.js b/Tests/amd/issues-qunit-tests.js new file mode 100644 index 0000000..e9ea15a --- /dev/null +++ b/Tests/amd/issues-qunit-tests.js @@ -0,0 +1,94 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, viewmodel, updatedModel, modelResult; + module("Issue Tests", { + setup: function () { + //ko.viewmodel.options.logging = true; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + viewmodel = undefined + } + }); + + + test("Issue 19 - empty array throws an exception on update", function () { + + model = { items: [] }; + + updatedModel = { + items: [{ + id: 5, + text: "test" + }] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.items().length, 1); + + }); + + test("Issue 18 - toModel call fails to completely strip extended functions and internal properties", function () { + + model = { + items: [{ + id: 5, + text: "test" + }], + obj: { + text: "test" + } + }; + + viewmodel = ko.viewmodel.fromModel(model, { + id: ["{root}.items[i].id"], + extend: { + "{root}.obj": function (obj) { + obj.getTextLength = function () { + return obj.text().length; + }; + + obj.textLength = ko.computed(function () { + return obj.text().length; + }); + + } + } + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.items[0].hasOwnProperty("..idName"), false, "hasOwnProperty of internal idName property returns true"); + + deepEqual(typeof modelResult.obj.getTextLength, "undefined", "function not removed"); + deepEqual(modelResult.obj.hasOwnProperty("getTextLength"), false, "hasOwnProperty of function extension returns true"); + + deepEqual(typeof modelResult.obj.textLength, "undefined", "property extension not removed"); + deepEqual(modelResult.obj.hasOwnProperty("textLength"), false, "hasOwnProperty of property extension returns true"); + + }); + + + test("Issue 17 - toModel call fails to correctly unwrap fromModel with nested observableArray of strings", function () { + + model = { items: [["a", "b", "c", "d"]] }; + + viewmodel = ko.viewmodel.fromModel(model); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(model, modelResult); + + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/main-qunit-tests.js b/Tests/amd/main-qunit-tests.js new file mode 100644 index 0000000..cf3e09b --- /dev/null +++ b/Tests/amd/main-qunit-tests.js @@ -0,0 +1,48 @@ +require.config({ + paths: { + knockout: "http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.0/knockout-min", + qunit: "http://code.jquery.com/qunit/qunit-1.10.0", + viewmodel: "../../knockout.viewmodel" + }, + shim: { + qunit: { + exports: "QUnit", + init: function() { + QUnit.config.autoload = false; + QUnit.config.autostart = false; + } + } + } +}); + +require(["qunit", "amd-qunit-tests", "undefined-Basic-qunit-tests", + "undefined-Mapping-qunit-tests", "null-Basic-qunit-tests", + "null-Mapping-qunit-tests", "simpleTypes-qunit-tests", + "nestedObject-qunit-tests", "fromModel-Basic-qunit-tests", + "fromModel-Mapping-qunit-tests", "toModel-Basic-qunit-tests", + "fromModelToModel-Basic-qunit-tests", "updateFromModel-Basic-qunit-tests", + "issues-qunit-tests", "updateFromModel-Mapping-contiguous-qunit-tests", + "updateFromModel-Mapping-noncontiguous-qunit-tests"], +function(QUnit, amd, undefinedBasic, undefinedMapping, nullBasic, nullMapping, + simpleTypes, nestedObjects, fromModelBasic, fromModelMapping, toModelBasic, + fromModelToModelBasic, updateFromModelBasic, issues, + updateFromModelMappingContiguous, updateFromModelMappingNoncontiguous) { + QUnit.load(); + QUnit.start(); + + amd.run(); + undefinedBasic.run(); + undefinedMapping.run(); + nullBasic.run(); + nullMapping.run(); + simpleTypes.run(); + nestedObjects.run(); + fromModelBasic.run(); + fromModelMapping.run(); + toModelBasic.run(); + fromModelToModelBasic.run(); + updateFromModelBasic.run(); + issues.run(); + updateFromModelMappingContiguous.run(); + updateFromModelMappingNoncontiguous.run(); +}); diff --git a/Tests/amd/nestedObject-qunit-tests.js b/Tests/amd/nestedObject-qunit-tests.js new file mode 100644 index 0000000..6d823fa --- /dev/null +++ b/Tests/amd/nestedObject-qunit-tests.js @@ -0,0 +1,249 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Nested Object Types", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + data: { + stringProp: "test", + id: 5, + date: new Date("01/01/2001") + } + }; + + updatedModel = { + data: { + stringProp: "test2", + id: 6, + date: new Date("02/01/2002") + } + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + + test("Basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.data.stringProp(), model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date(), model.data.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp(), updatedModel.data.stringProp, "Update String Test"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date(), updatedModel.data.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}": function(root){ + root.isValid = ko.computed(function () { + return root.data.stringProp.isValid() && root.data.id.isValid() && root.data.date.isValid(); + }); + }, + "{root}.data.stringProp": function (stringProp) { + stringProp.isValid = ko.computed(function () { + return stringProp() && stringProp().length; + }); + }, + "{root}.data.id": function (id) { + id.isValid = ko.computed(function () { + return id() && id() > 0; + }); + }, + "{root}.data.date": function (date) { + date.isValid = ko.computed(function () { + return date() && date() < new Date(); + }); + } + } + }); + + deepEqual(viewmodel.data.stringProp(), model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date(), model.data.date, "From Model Date Test"); + deepEqual(viewmodel.isValid(), true, "Extension check"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp(), updatedModel.data.stringProp, "Update String Test"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date(), updatedModel.data.date, "Update Date Test"); + deepEqual(viewmodel.isValid(), true, "Extension check"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.isValid(), true, "Extension check"); + }); + + test("Append object", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}.data"] + }); + + deepEqual(viewmodel, model, "From Model Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + notEqual(viewmodel, updatedModel, "Update Fail Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, model, "Result"); + }); + + test("Append object property", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}.data.stringProp"] + }); + + deepEqual(viewmodel.data.stringProp, model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date(), model.data.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp, updatedModel.data.stringProp, "From Model String Test Fail"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date(), updatedModel.data.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.data.stringProp, updatedModel.data.stringProp, "To Model String Test"); + deepEqual(modelResult.data.id, updatedModel.data.id, "To Model Number Test"); + deepEqual(modelResult.data.date, updatedModel.data.date, "To Model Date Test"); + }); + + test("Custom basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.data.date": function (date) { + return date.valueOf(); + } + } + }); + + deepEqual(viewmodel.data.stringProp(), model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date, model.data.date.valueOf(), "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp(), updatedModel.data.stringProp, "Update String Test"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date, updatedModel.data.date.valueOf(), "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.data.stringProp, updatedModel.data.stringProp, "To Model String Test"); + deepEqual(modelResult.data.id, updatedModel.data.id, "To Model Number Test"); + deepEqual(modelResult.data.date, updatedModel.data.date.valueOf(), "To Model Date Test"); + }); + + test("Custom basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.data.date": function (date) { + return date.valueOf(); + } + } + }); + + deepEqual(viewmodel.data.stringProp(), model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date, model.data.date.valueOf(), "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp(), updatedModel.data.stringProp, "Update String Test"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date, updatedModel.data.date.valueOf(), "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.data.stringProp, updatedModel.data.stringProp, "To Model String Test"); + deepEqual(modelResult.data.id, updatedModel.data.id, "To Model Number Test"); + deepEqual(modelResult.data.date, updatedModel.data.date.valueOf(), "To Model Date Test"); + }); + + test("Custom map and unmap", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.data.date": { + map: function (date) { + return ko.observable(date.valueOf()); + }, + unmap: function(date){ + return new Date(date()); + } + } + } + }); + + deepEqual(viewmodel.data.stringProp(), model.data.stringProp, "From Model String Test"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date(), model.data.date.valueOf(), "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.data.stringProp(), updatedModel.data.stringProp, "Update String Test"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date(), updatedModel.data.date.valueOf(), "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.data.stringProp, updatedModel.data.stringProp, "To Model String Test"); + deepEqual(modelResult.data.id, updatedModel.data.id, "To Model Number Test"); + deepEqual(modelResult.data.date, updatedModel.data.date, "To Model Date Test"); + }); + + test("Exclude", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + exclude: ["{root}.data.stringProp"] + }); + + equal(viewmodel.data.hasOwnProperty("stringProp"), false, "From Model String Prop Not Exist"); + deepEqual(viewmodel.data.id(), model.data.id, "From Model Number Test"); + deepEqual(viewmodel.data.date(), model.data.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + equal(viewmodel.data.hasOwnProperty("stringProp"), false, "Update... String Prop Not Exist"); + deepEqual(viewmodel.data.id(), updatedModel.data.id, "Update Number Test"); + deepEqual(viewmodel.data.date(), updatedModel.data.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + notEqual(modelResult.data.stringProp, updatedModel.data.stringProp, "To Model String Test"); + deepEqual(modelResult.data.id, updatedModel.data.id, "To Model Number Test"); + deepEqual(modelResult.data.date, updatedModel.data.date, "To Model Date Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/null-Basic-qunit-tests.js b/Tests/amd/null-Basic-qunit-tests.js new file mode 100644 index 0000000..ece464b --- /dev/null +++ b/Tests/amd/null-Basic-qunit-tests.js @@ -0,0 +1,69 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Basic Null Tests", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + Prop1: null, + Prop2: "test2", + Prop3: null, + Prop4: {}, + Prop5: null, + Prop6: [] + }; + + updatedModel = { + Prop1: "test2", + Prop2: null, + Prop3: {}, + Prop4: null, + Prop5: [], + Prop6: null + + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + + test("Basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.Prop1(), model.Prop1, "Null Prop Test");//1 + deepEqual(viewmodel.Prop2(), model.Prop2, "String Prop Test");//2 + deepEqual(viewmodel.Prop4, model.Prop4, "Object Prop Test");//3 + deepEqual(viewmodel.Prop6(), model.Prop6, "Array Prop Test");//4 + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1, "Null to Value Update Test");//5 + deepEqual(viewmodel.Prop2(), updatedModel.Prop2, "Value to Null Update Test");//6 + deepEqual(viewmodel.Prop3(), updatedModel.Prop3, "Null to Object Update Test");//7 + deepEqual(viewmodel.Prop4, updatedModel.Prop4, "Object to Null Update Test");//8 + deepEqual(viewmodel.Prop5(), updatedModel.Prop5, "Null to Array Update Test");//9 + notEqual(typeof viewmodel.Prop5.push, "function", "Null to Array Update Is Not Observable Array Test");//10 + deepEqual(viewmodel.Prop6(), updatedModel.Prop6, "Array to Null Update Test");//11 + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.Prop1, updatedModel.Prop1, "Null to Value Update Test");//12 + deepEqual(modelResult.Prop2, updatedModel.Prop2, "Value to Null Update Test");//13 + deepEqual(modelResult.Prop3, updatedModel.Prop3, "Null to Object Update Test");//14 + deepEqual(modelResult.Prop4, updatedModel.Prop4, "Object to Null Update Test");//15 + deepEqual(modelResult.Prop5, updatedModel.Prop5, "Null to Array Update Test");//16 + deepEqual(modelResult.Prop6, updatedModel.Prop6, "Array to Null Update Test");//17 + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/null-Mapping-qunit-tests.js b/Tests/amd/null-Mapping-qunit-tests.js new file mode 100644 index 0000000..0bdaeaf --- /dev/null +++ b/Tests/amd/null-Mapping-qunit-tests.js @@ -0,0 +1,280 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Null Mapping Tests", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + Prop1: null, + Prop2: "test2", + Prop3: {}, + Prop4: null + }; + + updatedModel = { + Prop1: "test2", + Prop2: null, + Prop3: null, + Prop4: {} + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + test("Extend String Null", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop1": function (val) { + val.isValid = ko.computed(function () { + return val() != null && val().length > 0; + }); + }, + "{root}.Prop2": function (val) { + val.isValid = ko.computed(function () { + return val() != null && val().length > 0; + }); + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1, "Null Prop Test"); + deepEqual(viewmodel.Prop1.isValid(), false, "Null Prop Extend Test"); + deepEqual(viewmodel.Prop2(), model.Prop2, "String Prop Test"); + deepEqual(viewmodel.Prop2.isValid(), true, "Null Prop Extend Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1, "Null Prop Test"); + deepEqual(viewmodel.Prop1.isValid(), true, "Null Prop Extend Test"); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2, "String Prop Test"); + deepEqual(viewmodel.Prop2.isValid(), false, "Null Prop Extend Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend Object Null", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop3": function (val) { + val.isValid = ko.computed(function () { + return ko.utils.unwrapObservable(val) != null; + }); + }, + "{root}.Prop4": function (val) { + val.isValid = ko.computed(function () { + return ko.utils.unwrapObservable(val) != null; + }); + } + } + }); + + deepEqual(typeof viewmodel.Prop3, "object", "Object Prop Test"); + deepEqual(viewmodel.Prop3.isValid(), true, "Object Prop Extend Test"); + deepEqual(viewmodel.Prop4(), null, "Null Prop Test"); + deepEqual(viewmodel.Prop4.isValid(), false, "Null Prop Extend Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop3, null, "Object to Null Prop Update Test"); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4, "Null to Object Update Prop Test"); + deepEqual(viewmodel.Prop4.isValid(), true, "Null to Object Update Prop Extend Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend Object Null", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop1": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop2": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop3": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop4": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + }); + + test("Append property", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}.Prop1", "{root}.Prop2"] + }); + + deepEqual(viewmodel.Prop1, model.Prop1, "Null to Value Update Test"); + deepEqual(viewmodel.Prop2, model.Prop2, "Value to Null Update Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + equal(viewmodel.Prop1, updatedModel.Prop1, "Null to Value Update Test"); + equal(viewmodel.Prop2, updatedModel.Prop2, "Value to Null Update Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + equal(modelResult.Prop1, updatedModel.Prop1, "Null to Value Update Test"); + deepEqual(modelResult.Prop2, updatedModel.Prop2, "Value to Null Update Test"); + + }); + + test("Custom basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.Prop1":function (val) { + return ko.observable(val); + }, + "{root}.Prop2": function (val) { + return ko.observable(val); + }, + "{root}.Prop3": function (val) { + return ko.observable(val); + }, + "{root}.Prop4": function (val) { + return ko.observable(val); + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + + }); + + test("Custom map and unmap", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.Prop1": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop2": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop3": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop4": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + }); + + test("Exclude", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + exclude: ["{root}.Prop2"] + }); + + equal(viewmodel.hasOwnProperty("Prop2"), false, "From Model String Prop Not Exist"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + equal(viewmodel.hasOwnProperty("Prop2"), false, "Update... String Prop Not Exist"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + equal(modelResult.hasOwnProperty("Prop2"), false, "Update... String Prop Not Exist"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/simpleTypes-qunit-tests.js b/Tests/amd/simpleTypes-qunit-tests.js new file mode 100644 index 0000000..0b99927 --- /dev/null +++ b/Tests/amd/simpleTypes-qunit-tests.js @@ -0,0 +1,218 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Simple Types", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + stringProp: "test", + id: 5, + date: new Date("01/01/2001") + }; + + updatedModel = { + stringProp: "test2", + id: 6, + date: new Date("02/01/2002") + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + + test("Basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.stringProp(), model.stringProp, "From Model String Test"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date(), model.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp(), updatedModel.stringProp, "Update String Test"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date(), updatedModel.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}": function(root){ + root.isValid = ko.computed(function () { + return root.stringProp.isValid() && root.id.isValid() && root.date.isValid(); + }); + }, + "{root}.stringProp": function (stringProp) { + stringProp.isValid = ko.computed(function () { + return stringProp() && stringProp().length; + }); + }, + "{root}.id": function (id) { + id.isValid = ko.computed(function () { + return id() && id() > 0; + }); + }, + "{root}.date": function (date) { + date.isValid = ko.computed(function () { + return date() && date() < new Date(); + }); + } + } + }); + + deepEqual(viewmodel.stringProp(), model.stringProp, "From Model String Test"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date(), model.date, "From Model Date Test"); + deepEqual(viewmodel.isValid(), true, "Extension check"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp(), updatedModel.stringProp, "Update String Test"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date(), updatedModel.date, "Update Date Test"); + deepEqual(viewmodel.isValid(), true, "Extension check"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.isValid(), true, "Extension check"); + }); + + test("Append root", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}"] + }); + + deepEqual(viewmodel, model, "From Model Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + notEqual(viewmodel, updatedModel, "Update Fail Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, model, "Result"); + }); + + test("Append property", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}.stringProp"] + }); + + deepEqual(viewmodel.stringProp, model.stringProp, "From Model String Test"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date(), model.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp, updatedModel.stringProp, "From Model String Test Fail"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date(), updatedModel.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, updatedModel.stringProp, "To Model String Test"); + deepEqual(modelResult.id, updatedModel.id, "To Model Number Test"); + deepEqual(modelResult.date, updatedModel.date, "To Model Date Test"); + }); + + test("Custom basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.date":function (date) { + return date.valueOf(); + } + } + }); + + deepEqual(viewmodel.stringProp(), model.stringProp, "From Model String Test"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date, model.date.valueOf(), "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp(), updatedModel.stringProp, "Update String Test"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date, updatedModel.date.valueOf(), "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, updatedModel.stringProp, "To Model String Test"); + deepEqual(modelResult.id, updatedModel.id, "To Model Number Test"); + deepEqual(modelResult.date, updatedModel.date.valueOf(), "To Model Date Test"); + }); + + test("Custom map and unmap", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.date":{ + map: function (date) { + return ko.observable(date.valueOf()); + }, + unmap: function(date){ + return new Date(date()); + } + } + } + }); + + deepEqual(viewmodel.stringProp(), model.stringProp, "From Model String Test"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date(), model.date.valueOf(), "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp(), updatedModel.stringProp, "Update String Test"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date(), updatedModel.date.valueOf(), "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, updatedModel.stringProp, "To Model String Test"); + deepEqual(modelResult.id, updatedModel.id, "To Model Number Test"); + deepEqual(modelResult.date, updatedModel.date, "To Model Date Test"); + }); + + test("Exclude", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + exclude: ["{root}.stringProp"] + }); + + equal(viewmodel.hasOwnProperty("stringProp"), false, "From Model String Prop Not Exist"); + deepEqual(viewmodel.id(), model.id, "From Model Number Test"); + deepEqual(viewmodel.date(), model.date, "From Model Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + equal(viewmodel.hasOwnProperty("stringProp"), false, "Update... String Prop Not Exist"); + deepEqual(viewmodel.id(), updatedModel.id, "Update Number Test"); + deepEqual(viewmodel.date(), updatedModel.date, "Update Date Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + notEqual(modelResult.stringProp, updatedModel.stringProp, "To Model String Test"); + deepEqual(modelResult.id, updatedModel.id, "To Model Number Test"); + deepEqual(modelResult.date, updatedModel.date, "To Model Date Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/toModel-Basic-qunit-tests.js b/Tests/amd/toModel-Basic-qunit-tests.js new file mode 100644 index 0000000..a7a0693 --- /dev/null +++ b/Tests/amd/toModel-Basic-qunit-tests.js @@ -0,0 +1,162 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("toModel Basic", { + setup: function () { + //ko.viewmodel.options.logging = true; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + + test("Default Basic Types", function () { + var viewmodel, modelResult; + + viewmodel = { + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observableArray([]) + }; + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, viewmodel.stringProp(), "String Test"); + deepEqual(modelResult.number, viewmodel.number(), "Number Test"); + deepEqual(modelResult.date, viewmodel.date(), "Date Test"); + deepEqual(modelResult.emptyArray, viewmodel.emptyArray(), "Array Test"); + }); + + test("Default Nested Object", function () { + var viewmodel, modelResult; + + viewmodel = { + nestedObject: { + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observable([]) + } + }; + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedObject.stringProp, viewmodel.nestedObject.stringProp(), "String Test"); + deepEqual(modelResult.nestedObject.number, viewmodel.nestedObject.number(), "Number Test"); + deepEqual(modelResult.nestedObject.date, viewmodel.nestedObject.date(), "Date Test"); + deepEqual(modelResult.nestedObject.emptyArray, viewmodel.nestedObject.emptyArray(), "Array Test"); + }); + + test("Default Object Array", function () { + var viewmodel, modelResult; + + viewmodel = { + objectArray: ko.observableArray([ + { + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observableArray([]) + } + ]) + }; + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.objectArray[0].stringProp, viewmodel.objectArray()[0].stringProp(), "String Test"); + deepEqual(modelResult.objectArray[0].number, viewmodel.objectArray()[0].number(), "Number Test"); + deepEqual(modelResult.objectArray[0].date, viewmodel.objectArray()[0].date(), "Date Test"); + deepEqual(modelResult.objectArray[0].emptyArray, viewmodel.objectArray()[0].emptyArray(), "Array Test"); + }); + + test("Default Nested Array", function () { + var viewmodel, modelResult; + + viewmodel = { + nestedArray: ko.observableArray([ko.observableArray([])]) + }; + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedArray[0], viewmodel.nestedArray()[0](), "Array Test"); + }); + + + + + test("Observable Object Basic Types", function () { + var viewmodel, modelResult; + + viewmodel = ko.observable({ + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observableArray([]) + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.stringProp, viewmodel().stringProp(), "String Test"); + deepEqual(modelResult.number, viewmodel().number(), "Number Test"); + deepEqual(modelResult.date, viewmodel().date(), "Date Test"); + deepEqual(modelResult.emptyArray, viewmodel().emptyArray(), "Array Test"); + }); + + test("Observable Object Nested Object", function () { + var viewmodel, modelResult; + + viewmodel = ko.observable({ + nestedObject: ko.observable({ + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observable([]) + }) + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedObject.stringProp, viewmodel().nestedObject().stringProp(), "String Test"); + deepEqual(modelResult.nestedObject.number, viewmodel().nestedObject().number(), "Number Test"); + deepEqual(modelResult.nestedObject.date, viewmodel().nestedObject().date(), "Date Test"); + deepEqual(modelResult.nestedObject.emptyArray, viewmodel().nestedObject().emptyArray(), "Array Test"); + }); + + test("Observable Object Object Array", function () { + var viewmodel, modelResult; + + viewmodel = ko.observable({ + objectArray: ko.observableArray([ + ko.observable({ + stringProp: ko.observable("test"), + number: ko.observable(5), + date: ko.observable(new Date("01/01/2001")), + emptyArray: ko.observableArray([]) + }) + ]) + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.objectArray[0].stringProp, viewmodel().objectArray()[0]().stringProp(), "String Test"); + deepEqual(modelResult.objectArray[0].number, viewmodel().objectArray()[0]().number(), "Number Test"); + deepEqual(modelResult.objectArray[0].date, viewmodel().objectArray()[0]().date(), "Date Test"); + deepEqual(modelResult.objectArray[0].emptyArray, viewmodel().objectArray()[0]().emptyArray(), "Array Test"); + }); + + test("Observable Object Nested Array", function () { + var viewmodel, modelResult; + + viewmodel = ko.observable({ + nestedArray: ko.observableArray([ko.observableArray([])]) + }); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.nestedArray[0], viewmodel().nestedArray()[0](), "Array Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/undefined-Basic-qunit-tests.js b/Tests/amd/undefined-Basic-qunit-tests.js new file mode 100644 index 0000000..92e3f85 --- /dev/null +++ b/Tests/amd/undefined-Basic-qunit-tests.js @@ -0,0 +1,69 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Basic Undefined Tests", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + Prop1: undefined, + Prop2: "test2", + Prop3: undefined, + Prop4: {}, + Prop5: undefined, + Prop6: [] + }; + + updatedModel = { + Prop1: "test2", + Prop2: undefined, + Prop3: {}, + Prop4: undefined, + Prop5: [], + Prop6: undefined + + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + + test("Basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.Prop1(), model.Prop1, "Undefined Prop Test");//1 + deepEqual(viewmodel.Prop2(), model.Prop2, "String Prop Test");//2 + deepEqual(viewmodel.Prop4, model.Prop4, "Object Prop Test");//3 + deepEqual(viewmodel.Prop6(), model.Prop6, "Array Prop Test");//4 + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1, "Undefined to Value Update Test");//5 + deepEqual(viewmodel.Prop2(), updatedModel.Prop2, "Value to Undefined Update Test");//6 + deepEqual(viewmodel.Prop3(), updatedModel.Prop3, "Undefined to Object Update Test");//7 + deepEqual(viewmodel.Prop4, updatedModel.Prop4, "Object to Undefined Update Test");//8 + deepEqual(viewmodel.Prop5(), updatedModel.Prop5, "Undefined to Array Update Test");//9 + notEqual(typeof viewmodel.Prop5.push, "function", "Undefined to Array Update Is Not Observable Array Test");//10 + deepEqual(viewmodel.Prop6(), updatedModel.Prop6, "Array to Undefined Update Test");//11 + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.Prop1, updatedModel.Prop1, "Undefined to Value Update Test");//12 + deepEqual(modelResult.Prop2, updatedModel.Prop2, "Value to Undefined Update Test");//13 + deepEqual(modelResult.Prop3, updatedModel.Prop3, "Undefined to Object Update Test");//14 + deepEqual(modelResult.Prop4, updatedModel.Prop4, "Object to Undefined Update Test");//15 + deepEqual(modelResult.Prop5, updatedModel.Prop5, "Undefined to Array Update Test");//16 + deepEqual(modelResult.Prop6, updatedModel.Prop6, "Array to Undefined Update Test");//17 + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/undefined-Mapping-qunit-tests.js b/Tests/amd/undefined-Mapping-qunit-tests.js new file mode 100644 index 0000000..038ee8c --- /dev/null +++ b/Tests/amd/undefined-Mapping-qunit-tests.js @@ -0,0 +1,280 @@ +/// +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedModel, modelResult; + module("Undefined Mapping Tests", { + setup: function () { + //ko.viewmodel.options.logging = true; + + model = { + Prop1: undefined, + Prop2: "test2", + Prop3: {}, + Prop4: undefined + }; + + updatedModel = { + Prop1: "test2", + Prop2: undefined, + Prop3: undefined, + Prop4: {} + }; + + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + model = undefined; + updatedModel = undefined; + modelResult = undefined; + } + }); + + test("Extend String Undefined", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop1": function (val) { + val.isValid = ko.computed(function () { + return val() != undefined && val().length > 0; + }); + }, + "{root}.Prop2": function (val) { + val.isValid = ko.computed(function () { + return val() != undefined && val().length > 0; + }); + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1, "Undefined Prop Test"); + deepEqual(viewmodel.Prop1.isValid(), false, "Undefined Prop Extend Test"); + deepEqual(viewmodel.Prop2(), model.Prop2, "String Prop Test"); + deepEqual(viewmodel.Prop2.isValid(), true, "Undefined Prop Extend Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1, "Undefined Prop Test"); + deepEqual(viewmodel.Prop1.isValid(), true, "Undefined Prop Extend Test"); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2, "String Prop Test"); + deepEqual(viewmodel.Prop2.isValid(), false, "Undefined Prop Extend Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend Object Undefined", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop3": function (val) { + val.isValid = ko.computed(function () { + return ko.utils.unwrapObservable(val) != undefined; + }); + }, + "{root}.Prop4": function (val) { + val.isValid = ko.computed(function () { + return ko.utils.unwrapObservable(val) != undefined; + }); + } + } + }); + + deepEqual(typeof viewmodel.Prop3, "object", "Object Prop Test"); + deepEqual(viewmodel.Prop3.isValid(), true, "Object Prop Extend Test"); + deepEqual(viewmodel.Prop4(), undefined, "Undefined Prop Test"); + deepEqual(viewmodel.Prop4.isValid(), false, "Undefined Prop Extend Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop3, undefined, "Object to Undefined Prop Update Test"); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4, "Undefined to Object Update Prop Test"); + deepEqual(viewmodel.Prop4.isValid(), true, "Undefined to Object Update Prop Extend Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult, updatedModel, "Result Object Comparison"); + }); + + test("Extend Object Undefined", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + extend: { + "{root}.Prop1": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop2": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop3": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + }, + "{root}.Prop4": function (val) { + if (!ko.isObservable(val)) { + val = ko.observable(val) + } + return val; + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + }); + + test("Append property", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + append: ["{root}.Prop1", "{root}.Prop2"] + }); + + deepEqual(viewmodel.Prop1, model.Prop1, "Undefined to Value Update Test"); + deepEqual(viewmodel.Prop2, model.Prop2, "Value to Undefined Update Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1, updatedModel.Prop1, "Undefined to Value Update Test"); + deepEqual(viewmodel.Prop2, updatedModel.Prop2, "Value to Undefined Update Test"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(modelResult.Prop1, updatedModel.Prop1, "Undefined to Value Update Test"); + deepEqual(modelResult.Prop2, updatedModel.Prop2, "Value to Undefined Update Test"); + + }); + + test("Custom basic", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.Prop1":function (val) { + return ko.observable(val); + }, + "{root}.Prop2": function (val) { + return ko.observable(val); + }, + "{root}.Prop3": function (val) { + return ko.observable(val); + }, + "{root}.Prop4": function (val) { + return ko.observable(val); + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + + }); + + test("Custom map and unmap", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + custom: { + "{root}.Prop1": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop2": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop3": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + }, + "{root}.Prop4": { + map: function (val) { + return ko.observable(val); + }, + unmap: function (val) { + return val(); + } + } + } + }); + + deepEqual(viewmodel.Prop1(), model.Prop1); + deepEqual(viewmodel.Prop2(), model.Prop2); + deepEqual(viewmodel.Prop3(), model.Prop3); + deepEqual(viewmodel.Prop4(), model.Prop4); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.Prop1(), updatedModel.Prop1); + deepEqual(viewmodel.Prop2(), updatedModel.Prop2); + deepEqual(viewmodel.Prop3(), updatedModel.Prop3); + deepEqual(viewmodel.Prop4(), updatedModel.Prop4); + + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(updatedModel, modelResult); + }); + + test("Exclude", function () { + + var viewmodel = ko.viewmodel.fromModel(model, { + exclude: ["{root}.Prop2"] + }); + + equal(viewmodel.hasOwnProperty("Prop2"), false, "From Model String Prop Not Exist"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + equal(viewmodel.hasOwnProperty("Prop2"), false, "Update... String Prop Not Exist"); + + modelResult = ko.viewmodel.toModel(viewmodel); + + equal(modelResult.hasOwnProperty("Prop2"), false, "Update... String Prop Not Exist"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/updateFromModel-Basic-qunit-tests.js b/Tests/amd/updateFromModel-Basic-qunit-tests.js new file mode 100644 index 0000000..bc82929 --- /dev/null +++ b/Tests/amd/updateFromModel-Basic-qunit-tests.js @@ -0,0 +1,286 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + module("updateFromModel Basic", { + setup: function () { + //ko.viewmodel.options.logging = true; + }, + teardown: function () { + //ko.viewmodel.options.logging = false; + } + }); + + + test("Default simple types", function () { + var model, updatedModel, viewmodel; + + model = { + stringProp: "test", + number: 5, + date: new Date("01/01/2001") + }; + + updatedModel = { + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + }; + + viewmodel = ko.viewmodel.fromModel(model); + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.stringProp(), updatedModel.stringProp, "String Test"); + deepEqual(viewmodel.number(), updatedModel.number, "Number Test"); + deepEqual(viewmodel.date(), updatedModel.date, "Date Test"); + }); + + test("nested object simple types", function () { + var model, updatedModel, viewmodel; + + model = { + test:{ + stringProp: "test", + number: 5, + date: new Date("01/01/2001") + } + }; + + updatedModel = { + test: { + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + } + }; + + viewmodel = ko.viewmodel.fromModel(model); + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.test.stringProp(), updatedModel.test.stringProp, "String Test"); + deepEqual(viewmodel.test.number(), updatedModel.test.number, "Number Test"); + deepEqual(viewmodel.test.date(), updatedModel.test.date, "Date Test"); + }); + + test("nested object override success simple types", function () { + var model, updatedModel, viewmodel, options; + + options = { + override:["{root}.test"] + }; + + model = { + test: { + stringProp: "test", + number: 5, + date: new Date("01/01/2001") + } + }; + + updatedModel = { + test: { + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + } + }; + + viewmodel = ko.viewmodel.fromModel(model, options); + deepEqual(viewmodel.test.stringProp(), model.test.stringProp, "Viewmodel String Test"); + deepEqual(viewmodel.test.number(), model.test.number, "Viewmodel Number Test"); + deepEqual(viewmodel.test.date(), model.test.date, "Viewmodel Date Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.test.stringProp(), updatedModel.test.stringProp, "UpdatedModel String Test"); + deepEqual(viewmodel.test.number(), updatedModel.test.number, "UpdatedModel Number Test"); + deepEqual(viewmodel.test.date(), updatedModel.test.date, "UpdatedModel Date Test"); + }); + + //if undefined then we are using facade around ko.mapping + //used to exclude tests that are incompatable with ko.mapping + if (ko.viewmodel.options.mappingCompatability !== undefined) { + test("ID arrayChildId match array object simple types", function () { + var model, updatedModel, viewmodel, options, originalArrayItem; + + model = { + items: [ + { + id: 5, + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + updatedModel = { + items: [ + { + id: 5, + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + }, + { + id: 6, + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + options = { + arrayChildId: { + "{root}.items": "id" + } + }; + + viewmodel = ko.viewmodel.fromModel(model, options); + originalArrayItem = viewmodel.items()[0]; + deepEqual(originalArrayItem.id(), 5, "verify id before update"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.items()[0].id(), 5, "verify id after update"); + deepEqual(viewmodel.items()[0] === originalArrayItem, true, "verify still same object"); + deepEqual(viewmodel.items()[0].id(), updatedModel.items[0].id, "String Test"); + deepEqual(viewmodel.items()[0].stringProp(), updatedModel.items[0].stringProp, "String Test"); + deepEqual(viewmodel.items()[0].number(), updatedModel.items[0].number, "String Test"); + deepEqual(viewmodel.items()[0].date(), updatedModel.items[0].date, "String Test"); + }); + } + + test("No arrayChildId option array object simple types", function () { + var model, updatedModel, viewmodel, options, originalArrayItem; + + model = { + items: [ + { + id: 5, + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + updatedModel = { + items: [ + { + id: 5, + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + }, + { + id: 6, + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + options = {}; + + viewmodel = ko.viewmodel.fromModel(model, options); + originalArrayItem = viewmodel.items()[0]; + deepEqual(originalArrayItem.id(), 5, "verify id before update"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.items()[0].id(), 5, "verify id after update"); + deepEqual(viewmodel.items()[0] !== originalArrayItem, true, "verify not still same object"); + deepEqual(viewmodel.items()[0].id(), updatedModel.items[0].id, "String Test"); + deepEqual(viewmodel.items()[0].stringProp(), updatedModel.items[0].stringProp, "String Test"); + deepEqual(viewmodel.items()[0].number(), updatedModel.items[0].number, "String Test"); + deepEqual(viewmodel.items()[0].date(), updatedModel.items[0].date, "String Test"); + }); + + test("arrayChildId option swapped array item item", function () { + var model, updatedModel, viewmodel, options, originalArrayItem; + + model = { + items: [ + { + id: 4, + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + updatedModel = { + items: [ + { + id: 5, + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + } + ] + }; + + options = { + arrayChildId: { + "{root}.items": "id" + } + } + + viewmodel = ko.viewmodel.fromModel(model, options); + + originalArrayItem = viewmodel.items()[0]; + deepEqual(originalArrayItem.id(), 4, "verify id before update"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.items()[0] !== originalArrayItem, true, "verify not still same object"); + deepEqual(viewmodel.items()[0].id(), 5, "verify id before update"); + deepEqual(viewmodel.items().length, updatedModel.items.length, "Array Length Test"); + deepEqual(viewmodel.items()[0].id(), updatedModel.items[0].id, "Array Item id Test"); + deepEqual(viewmodel.items()[0].stringProp(), updatedModel.items[0].stringProp, "String Test"); + deepEqual(viewmodel.items()[0].number(), updatedModel.items[0].number, "String Test"); + deepEqual(viewmodel.items()[0].date(), updatedModel.items[0].date, "String Test"); + }); + test("array item item", function () { + var model, updatedModel, viewmodel, options; + + model = { + items: [ + { + stringProp: "test", + number: 3, + date: new Date("2/04/2001") + } + ] + }; + + updatedModel = { + items: [ + { + stringProp: "test2", + number: 6, + date: new Date("12/04/2001") + } + ] + }; + + viewmodel = ko.viewmodel.fromModel(model); + + deepEqual(viewmodel.items().length, model.items.length, "Array Length Test"); + deepEqual(viewmodel.items()[0].stringProp(), model.items[0].stringProp, "String Test"); + deepEqual(viewmodel.items()[0].number(), model.items[0].number, "String Test"); + deepEqual(viewmodel.items()[0].date(), model.items[0].date, "String Test"); + + ko.viewmodel.updateFromModel(viewmodel, updatedModel); + + deepEqual(viewmodel.items().length, updatedModel.items.length, "Array Length Test"); + deepEqual(viewmodel.items()[0].stringProp(), updatedModel.items[0].stringProp, "String Test"); + deepEqual(viewmodel.items()[0].number(), updatedModel.items[0].number, "String Test"); + deepEqual(viewmodel.items()[0].date(), updatedModel.items[0].date, "String Test"); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/updateFromModel-Mapping-contiguous-qunit-tests.js b/Tests/amd/updateFromModel-Mapping-contiguous-qunit-tests.js new file mode 100644 index 0000000..9a704a1 --- /dev/null +++ b/Tests/amd/updateFromModel-Mapping-contiguous-qunit-tests.js @@ -0,0 +1,54 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedmodel, viewmodel, customMapping, originalChildObject, modelResult, actual, newChildObject; + + module("updateFromModel Contiguous Object Updates", { + setup: function () { + + }, + teardown: function () { + + } + }); + + + model = { items: [{ test: 5, Id: 3 }] }; + + var customMapping = { + extend: { + "{root}.items[i]": function (item) { + return ko.observable(item); + } + }, + arrayChildId: { + "{root}.items": "Id" + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + originalChildObject = viewmodel.items()[0](); + + updatedmodel = { items: [{ test: 9, Id: 3 }] }; + test("Contiguous Test Fail", function () { + + newChildObject = viewmodel.items()[0](); + modelResult = ko.viewmodel.toModel(viewmodel); + + notEqual(viewmodel.items()[0]().test(), updatedmodel.items[0].test, "Test value has been updated in viewmodel"); + }); + + + test("Contiguous Test Pass", function () { + ko.viewmodel.updateFromModel(viewmodel, updatedmodel); + newChildObject = viewmodel.items()[0](); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0]().test(), updatedmodel.items[0].test, "Test value has been updated in viewmodel"); + deepEqual(updatedmodel, modelResult, "Updated model and toModel have same values"); + ok(originalChildObject == newChildObject, "Original child object was updated and not replaced."); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/updateFromModel-Mapping-noncontiguous-qunit-tests.js b/Tests/amd/updateFromModel-Mapping-noncontiguous-qunit-tests.js new file mode 100644 index 0000000..8f06894 --- /dev/null +++ b/Tests/amd/updateFromModel-Mapping-noncontiguous-qunit-tests.js @@ -0,0 +1,51 @@ +define(['knockout', 'viewmodel'], function (ko) { + var run = function () { + var model, updatedmodel, viewmodel, customMapping, originalChildObject, modelResult, actual, newChildObject; + + module("updateFromModel Noncontiguous Object Updates", { + setup: function () { + + }, + teardown: function () { + + } + }); + + + model = { items: [{ test: 5, Id: 3 }] }; + + var customMapping = { + extend: { + "{root}.items[i]": function (item) { + return ko.observable(item); + } + }, + arrayChildId: { + "{root}.items": "Id" + } + }; + + viewmodel = ko.viewmodel.fromModel(model, customMapping); + + originalChildObject = viewmodel.items()[0](); + + + updatedmodel = { items: [{ test: 7, Id: 3 }] }; + + ko.viewmodel.updateFromModel(viewmodel, updatedmodel, true).onComplete(function () { + asyncTest("Noncontiguous Test", function () { + expect(4); + newChildObject = viewmodel.items()[0](); + modelResult = ko.viewmodel.toModel(viewmodel); + + deepEqual(viewmodel.items()[0]().test(), updatedmodel.items[0].test, "Test value has been updated in viewmodel"); + deepEqual(viewmodel.items()[0]().test(), 7, "Test value is what was expected"); + deepEqual(updatedmodel, modelResult, "Updated model and toModel have same values"); + ok(originalChildObject == newChildObject, "Original child object was updated and not replaced."); + start(); + }); + }); + }; + + return {run: run} +}); diff --git a/Tests/amd/updateFromModel-Mapping-qunit-tests.js b/Tests/amd/updateFromModel-Mapping-qunit-tests.js new file mode 100644 index 0000000..e69de29