UNPKG

can-define

Version:

Create observable objects with JS dot operator compatibility

1,640 lines (1,292 loc) 39.9 kB
var QUnit = require("steal-qunit"); var DefineMap = require("can-define/map/map"); var define = require("can-define"); var Observation = require("can-observation"); var assign = require("can-assign"); var canReflect = require("can-reflect"); var canSymbol = require("can-symbol"); var isPlainObject = canReflect.isPlainObject; var canTestHelpers = require("can-test-helpers/lib/dev"); var DefineList = require("can-define/list/list"); var dev = require("can-log/dev/dev"); var ObservationRecorder = require("can-observation-recorder"); var MaybeString = require("can-data-types/maybe-string/maybe-string"); var sealWorks = (function() { try { var o = {}; Object.seal(o); o.prop = true; return false; } catch(e) { return true; } })(); QUnit.module("can-define/map/map"); QUnit.test("Map is an event emitter", function (assert) { var Base = DefineMap.extend({}); assert.ok(Base.on, 'Base has event methods.'); var Map = Base.extend({}); assert.ok(Map.on, 'Map has event methods.'); }); QUnit.test("creating an instance", function(assert) { var map = new DefineMap({prop: "foo"}); map.on("prop", function(ev, newVal, oldVal){ assert.equal(newVal, "BAR"); assert.equal(oldVal, "foo"); }); map.prop = "BAR"; }); QUnit.test("creating an instance with nested prop", function(assert) { var map = new DefineMap({name: {first: "Justin"}}); map.name.on("first", function(ev, newVal, oldVal){ assert.equal(newVal, "David"); assert.equal(oldVal, "Justin"); }); map.name.first = "David"; }); QUnit.test("extending", function(assert) { var MyMap = DefineMap.extend({ prop: {} }); var map = new MyMap(); map.on("prop", function(ev, newVal, oldVal){ assert.equal(newVal, "BAR"); assert.equal(oldVal, undefined); }); map.prop = "BAR"; }); QUnit.test("loop only through defined serializable props", function(assert) { var MyMap = DefineMap.extend({ propA: {}, propB: {serialize: false}, propC: { get: function(){ return this.propA; } } }); var inst = new MyMap({propA: 1, propB: 2}); assert.deepEqual(Object.keys(inst.get()), ["propA"]); }); QUnit.test("get and set can setup expandos", function(assert) { var map = new DefineMap(); var oi = new Observation(function(){ return map.get("foo"); }); canReflect.onValue(oi, function(newVal){ assert.equal(newVal, "bar", "updated to bar"); canReflect.offValue(oi); }); map.set("foo","bar"); }); QUnit.test("default settings", function(assert) { var MyMap = DefineMap.extend({ "*": "string", foo: {} }); var m = new MyMap(); m.set("foo",123); assert.ok(m.get("foo") === "123"); }); QUnit.test("default settings on unsealed", function(assert) { var MyMap = DefineMap.extend({ seal: false },{ "*": "string" }); var m = new MyMap(); m.set("foo",123); assert.ok(m.get("foo") === "123"); }); if (!System.isEnv('production')) { QUnit.test("extends sealed objects (#48)", function(assert) { var Map1 = DefineMap.extend({ seal: true }, { name: { get: function(curVal){ return "computed " + curVal; } } }); var Map2 = Map1.extend({ seal: false }, {}); var Map3 = Map2.extend({ seal: true }, {}); var map1 = new Map1({ name: "Justin" }); try { map1.foo = "bar"; if (map1.foo) { assert.ok(false, "map1 not sealed"); } else { assert.ok(true, "map1 sealed - silent failure"); } } catch(ex) { assert.ok(true, "map1 sealed"); } assert.equal(map1.name, "computed Justin", "map1.name property is computed"); var map2 = new Map2({ name: "Brian" }); try { map2.foo = "bar"; if (map2.foo) { assert.ok(true, "map2 not sealed"); } else { assert.ok(false, "map2 sealed"); } } catch (ex) { assert.ok(false, "map2 sealed"); } assert.equal(map2.name, "computed Brian", "map2.name property is computed"); var map3 = new Map3({ name: "Curtis" }); try { map3.foo = "bar"; if (map3.foo) { assert.ok(false, "map3 not sealed"); } else { assert.ok(true, "map3 sealed"); } } catch (ex) { assert.ok(true, "map3 sealed"); } assert.equal(map3.name, "computed Curtis", "map3.name property is computed"); }); } QUnit.test("get with dynamically added properties", function(assert) { var map = new DefineMap(); map.set("a",1); map.set("b",2); assert.deepEqual(map.get(), {a: 1, b: 2}); }); QUnit.test("set multiple props", function(assert) { var map = new DefineMap(); map.assign({a: 0, b: 2}); assert.deepEqual(map.get(), {a: 0, b: 2}, "added props"); map.update({a: 2}); assert.deepEqual(map.get(), {a: 2}, "removed b"); map.assign({foo: {bar: "VALUE"}}); assert.deepEqual(map.get(), {foo: {bar: "VALUE"}, a: 2}, "works nested"); }); QUnit.test("serialize responds to added props", function(assert) { var map = new DefineMap(); var oi = new Observation(function(){ return map.serialize(); }); canReflect.onValue(oi, function(newVal){ assert.deepEqual(newVal, {a: 1, b: 2}, "updated right"); canReflect.offValue(oi); }); map.assign({a: 1, b: 2}); }); QUnit.test("initialize an undefined property", function(assert) { var MyMap = DefineMap.extend({seal: false},{}); var instance = new MyMap({foo: "bar"}); assert.equal(instance.foo, "bar"); }); QUnit.test("set an already initialized null property", function(assert) { var map = new DefineMap({ foo: null }); map.assign({ foo: null }); assert.equal(map.foo, null); }); QUnit.test("creating a new key doesn't cause two changes", function(assert) { assert.expect(1); var map = new DefineMap(); var oi = new Observation(function(){ return map.serialize(); }); canReflect.onValue(oi, function(newVal){ assert.deepEqual(newVal, {a: 1}, "updated right"); canReflect.offValue(oi); }); map.set("a", 1); }); QUnit.test("setting nested object", function(assert) { var m = new DefineMap({}); m.assign({foo: {}}); m.assign({foo: {}}); assert.deepEqual(m.get(), {foo: {}}); }); QUnit.test("passing a DefineMap to DefineMap (#33)", function(assert) { var MyMap = DefineMap.extend({foo: "observable"}); var m = new MyMap({foo: {}, bar: {}}); var m2 = new MyMap(m); assert.deepEqual(m.get(), m2.get()); assert.ok(m.foo === m2.foo, "defined props the same"); assert.ok(m.bar === m2.bar, "expando props the same"); }); QUnit.test("serialize: function works (#38)", function(assert) { var Something = DefineMap.extend({}); var MyMap = DefineMap.extend({ somethingRef: { type: function(val){ return new Something({id: val}); }, serialize: function(val){ return val.id; } }, somethingElseRef: { type: function(val){ return new Something({id: val}); }, serialize: false } }); var myMap = new MyMap({somethingRef: 2, somethingElseRef: 3}); assert.ok(myMap.somethingRef instanceof Something); assert.deepEqual( myMap.serialize(), {somethingRef: 2}, "serialize: function and serialize: false works"); var MyMap2 = DefineMap.extend({ "*": { serialize: function(value){ return "" + value; } } }); var myMap2 = new MyMap2({foo: 1, bar: 2}); assert.deepEqual( myMap2.serialize(), {foo: "1", bar: "2"}, "serialize: function on default works"); }); QUnit.test("get will not create properties", function(assert) { var method = function(){}; var MyMap = DefineMap.extend({ method: method }); var m = new MyMap(); m.get("foo"); assert.equal(m.get("method"), method); }); QUnit.test("Properties are enumerable", function(assert) { assert.expect(4); var VM = DefineMap.extend({ foo: "string" }); var vm = new VM({ foo: "bar", baz: "qux" }); var i = 0; canReflect.eachKey(vm, function(value, key){ if(i === 0) { assert.equal(key, "foo"); assert.equal(value, "bar"); } else { assert.equal(key, "baz"); assert.equal(value, "qux"); } i++; }); }); QUnit.test("Getters are not enumerable", function(assert) { assert.expect(2); var MyMap = DefineMap.extend({ foo: "string", baz: { get: function(){ return this.foo; } } }); var map = new MyMap({ foo: "bar" }); canReflect.eachKey(map, function(value, key){ assert.equal(key, "foo"); assert.equal(value, "bar"); }); }); QUnit.test("extending DefineMap constructor functions (#18)", function(assert) { var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} }); var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} }); var CType = BType.extend("CType", { cProp: {}, cMethod: function(){} }); var map = new CType(); map.on("aProp", function(ev, newVal, oldVal){ assert.equal(newVal, "PROP"); assert.equal(oldVal, undefined); }); map.on("bProp", function(ev, newVal, oldVal){ assert.equal(newVal, "FOO"); assert.equal(oldVal, undefined); }); map.on("cProp", function(ev, newVal, oldVal){ assert.equal(newVal, "BAR"); assert.equal(oldVal, undefined); }); map.aProp = "PROP"; map.bProp = 'FOO'; map.cProp = 'BAR'; assert.ok(map.aMethod); assert.ok(map.bMethod); assert.ok(map.cMethod); }); QUnit.test("extending DefineMap constructor functions more than once (#18)", function(assert) { var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} }); var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} }); var CType = AType.extend("CType", { cProp: {}, cMethod: function(){} }); var map1 = new BType(); var map2 = new CType(); map1.on("aProp", function(ev, newVal, oldVal){ assert.equal(newVal, "PROP", "aProp newVal on map1"); assert.equal(oldVal, undefined); }); map1.on("bProp", function(ev, newVal, oldVal){ assert.equal(newVal, "FOO", "bProp newVal on map1"); assert.equal(oldVal, undefined); }); map2.on("aProp", function(ev, newVal, oldVal){ assert.equal(newVal, "PROP", "aProp newVal on map2"); assert.equal(oldVal, undefined); }); map2.on("cProp", function(ev, newVal, oldVal){ assert.equal(newVal, "BAR", "cProp newVal on map2"); assert.equal(oldVal, undefined); }); map1.aProp = "PROP"; map1.bProp = 'FOO'; map2.aProp = "PROP"; map2.cProp = 'BAR'; assert.ok(map1.aMethod, "map1 aMethod"); assert.ok(map1.bMethod); assert.ok(map2.aMethod); assert.ok(map2.cMethod, "map2 cMethod"); }); QUnit.test("extending DefineMap constructor functions - value (#18)", function(assert) { var AType = DefineMap.extend("AType", { aProp: {default: 1} }); var BType = AType.extend("BType", { }); var CType = BType.extend("CType",{ }); var c = new CType(); assert.equal( c.aProp , 1 ,"got initial value" ); }); QUnit.test("copying DefineMap excludes constructor", function(assert) { var AType = DefineMap.extend("AType", { aProp: {default: 1} }); var a = new AType(); var b = assign({}, a); assert.notEqual(a.constructor, b.constructor, "Constructor prop not copied"); assert.equal(a.aProp, b.aProp, "Other values are unaffected"); }); QUnit.test("cloning from non-defined map excludes special keys on setup", function(assert) { var MyType = DefineMap.extend({ }); var a = new MyType({ "foo": "bar" }); var b = new DefineMap(a); assert.notEqual(a.constructor, b.constructor, "Constructor prop not copied"); assert.notEqual(a._data, b._data, "_data prop not copied"); assert.equal(a.foo, b.foo, "Other props copied"); }); QUnit.test("copying from .set() excludes special keys", function(assert) { var MyType = DefineMap.extend({}); var a = new MyType({ "foo": "bar", "existing": "newVal" }); var b = new DefineMap({ "existing": "oldVal" }); b.assign(a); assert.notEqual(a.constructor, b.constructor, "Constructor prop not copied"); assert.notEqual(a._data, b._data, "_data prop not copied"); assert.equal(a.foo, b.foo, "NEw props copied"); }); QUnit.test("copying with assign() excludes special keys", function(assert) { var a = { _data: {}, "foo": "bar", "existing": "neVal" }; var b = new DefineMap({ "existing": "oldVal" }, false); canReflect.assignMap(b, a); assert.notEqual(a._data, b._data, "_data prop not copied"); assert.equal(a.foo, b.foo, "New props copied"); assert.equal(a.existing, b.existing, "Existing props copied"); }); QUnit.test("shorthand getter setter (#56)", function(assert) { var Person = DefineMap.extend({ first: "*", last: "*", get fullName() { return this.first + " " + this.last; }, set fullName(newVal){ var parts = newVal.split(" "); this.first = parts[0]; this.last = parts[1]; } }); var p = new Person({first: "Mohamed", last: "Cherif"}); p.on("fullName", function(ev, newVal, oldVal) { assert.equal(oldVal, "Mohamed Cherif"); assert.equal(newVal, "Justin Meyer"); }); assert.equal(p.fullName, "Mohamed Cherif", "fullName initialized right"); p.fullName = "Justin Meyer"; }); QUnit.test('compute props can be set to null or undefined (#2372)', function(assert) { var VM = DefineMap.extend({ computeProp: { type: 'compute' } }); var vmNull = new VM({computeProp: null}); assert.equal(vmNull.get('computeProp'), null, 'computeProp is null, no error thrown'); var vmUndef = new VM({computeProp: undefined}); assert.equal(vmUndef.get('computeProp'), undefined, 'computeProp is undefined, no error thrown'); }); QUnit.test("Inheriting DefineMap .set doesn't work if prop is on base map (#74)", function(assert) { var Base = DefineMap.extend({ baseProp: "string" }); var Inheriting = Base.extend(); var inherting = new Inheriting(); inherting.set("baseProp", "value"); assert.equal(inherting.baseProp,"value", "set prop"); }); if(sealWorks && System.env.indexOf('production') < 0) { QUnit.test("setting not defined property", function(assert) { var MyMap = DefineMap.extend({ prop: {} }); var mymap = new MyMap(); try { mymap.notdefined = "value"; assert.ok(false, "no error"); } catch(e) { assert.ok(true, "error thrown"); } }); } QUnit.test(".extend errors when re-defining a property (#117)", function(assert) { var A = DefineMap.extend("A", { foo: { type: "string", default: "blah" } }); A.extend("B", { foo: { type: "string", default: "flub" } }); var C = DefineMap.extend("C", { foo: { get: function() { return "blah"; } } }); C.extend("D", { foo: { get: function() { return "flub"; } } }); assert.ok(true, "extended without errors"); }); QUnit.test(".value functions should not be observable", function(assert) { var outer = new DefineMap({ bam: "baz" }); var ItemsVM = DefineMap.extend({ item: { default: function(){ (function(){})(this.zed, outer.bam); return new DefineMap({ foo: "bar" }); } }, zed: "string" }); var items = new ItemsVM(); var count = 0; var itemsList = new Observation(function(){ count++; return items.item; }); canReflect.onValue(itemsList, function(){}); items.item.foo = "changed"; items.zed = "changed"; assert.equal(count, 1); }); QUnit.test(".value values are overwritten by props in DefineMap construction", function(assert) { var Foo = DefineMap.extend({ bar: { default: "baz" } }); var foo = new Foo({ bar: "quux" }); assert.equal(foo.bar, "quux", "Value set properly"); }); QUnit.test("can-reflect reflections work with DefineMap", function(assert) { var b = new DefineMap({ "foo": "bar" }); var c = new (DefineMap.extend({ "baz": { get: function() { return b.foo; } } }))({ "foo": "bar", thud: "baz" }); assert.equal( canReflect.getKeyValue(b, "foo"), "bar", "unbound value"); var handler = function(newValue){ assert.equal(newValue, "quux", "observed new value"); // Turn off the "foo" handler but "thud" should still be bound. canReflect.offKeyValue(c, "baz", handler); }; assert.ok(!canReflect.isValueLike(c), "isValueLike is false"); assert.ok(canReflect.isObservableLike(c), "isObservableLike is true"); assert.ok(canReflect.isMapLike(c), "isMapLike is true"); assert.ok(!canReflect.isListLike(c), "isListLike is false"); assert.ok( !canReflect.keyHasDependencies(b, "foo"), "keyHasDependencies -- false"); canReflect.onKeyValue(c, "baz", handler); // Do a second binding to check that you can unbind correctly. canReflect.onKeyValue(c, "thud", handler); assert.ok( canReflect.keyHasDependencies(c, "baz"), "keyHasDependencies -- true"); b.foo = "quux"; c.thud = "quux"; assert.equal( canReflect.getKeyValue(c, "baz"), "quux", "bound value"); // sanity checks to ensure that handler doesn't get called again. b.foo = "thud"; c.baz = "jeek"; }); QUnit.test("can-reflect setKeyValue", function(assert) { var a = new DefineMap({ "a": "b" }); canReflect.setKeyValue(a, "a", "c"); assert.equal(a.a, "c", "setKeyValue"); }); QUnit.test("can-reflect deleteKeyValue", function(assert) { var a = new DefineMap({ "a": "b" }); canReflect.deleteKeyValue(a, "a"); assert.equal(a.a, undefined, "value is now undefined"); assert.ok(!("a" in a.get()), "value not included in serial"); }); QUnit.test("can-reflect getKeyDependencies", function(assert) { var a = new DefineMap({ "a": "a" }); var b = new (DefineMap.extend({ "a": { get: function() { return a.a; } } }))(); // DefineMaps bind automatically without events, so this is already running. assert.ok(canReflect.getKeyDependencies(b, "a"), "dependencies exist"); assert.ok(!canReflect.getKeyDependencies(b, "b"), "no dependencies exist for unknown value"); assert.ok(canReflect.getKeyDependencies(b, "a").valueDependencies.has(b._computed.a.compute), "dependencies returned"); }); QUnit.test("can-reflect assign", function(assert) { var aData = { "a": "b" }; var bData = { "b": "c" }; var a = new DefineMap(aData); var b = new DefineMap(bData); canReflect.assign( a,b); assert.deepEqual(a.get(), assign(aData, bData), "when called with an object, should merge into existing object"); }); QUnit.test("Does not attempt to redefine _data if already defined", function(assert) { var Bar = DefineMap.extend({seal: false}, { baz: { default: "thud" } }); var baz = new Bar(); define(baz, { quux: { default: "jeek" }, plonk: { get: function() { return "waldo"; } } }, baz._define); assert.equal(baz.quux, "jeek", "New definitions successful"); assert.equal(baz.plonk, "waldo", "New computed definitions successful"); assert.equal(baz.baz, "thud", "Old definitions still available"); }); if (!System.isEnv('production')) { QUnit.test("redefines still not allowed on sealed objects", function(assert) { assert.expect(6); var Bar = DefineMap.extend({seal: true}, { baz: { default: "thud" } }); var baz = new Bar(); try { define(baz, { quux: { default: "jeek" } }, baz._define); } catch(e) { assert.ok(/is not extensible/i.test(e.message), "Sealed object throws on data property defines"); assert.ok(!Object.getOwnPropertyDescriptor(baz, "quux"), "nothing set on object"); assert.ok(!Object.getOwnPropertyDescriptor(baz._data, "quux"), "nothing set on _data"); } try { define(baz, { plonk: { get: function() { return "waldo"; } } }, baz._define); } catch(e) { assert.ok(/is not extensible/i.test(e.message), "Sealed object throws on computed property defines"); assert.ok(!Object.getOwnPropertyDescriptor(baz, "plonk"), "nothing set on object"); assert.ok(!Object.getOwnPropertyDescriptor(baz._computed, "plonk"), "nothing set on _computed"); } }); } QUnit.test("Call .get() when a nested object has its own get method", function(assert) { var Bar = DefineMap.extend({ request: "*" }); var request = { prop: 22, get: function(){ if(arguments.length === 0) { throw new Error("This function can't be called with 0 arguments"); } } }; var obj = new Bar({ request: request }); var data = obj.get(); assert.equal(data.request.prop, 22, "obj did get()"); }); QUnit.test("DefineMap short-hand Type (#221)", function(assert) { var Child = DefineMap.extend('child', { other: DefineMap }); var c = new Child(); c.other = { prop: 'hello' }; assert.ok(c.other instanceof DefineMap, "is a DefineMap"); }); QUnit.test("non-Object constructor", function(assert) { var Constructor = DefineMap.extend(); assert.ok(!isPlainObject(new DefineMap()), "instance of DefineMap is not a plain object"); assert.ok(!isPlainObject(new Constructor()), "instance of extended DefineMap is not a plain object"); }); QUnit.test('Observation bound to getter using lastSetVal updates correctly (canjs#3541)', function(assert) { var MyMap = DefineMap.extend({ foo: { get: function(lastSetVal) { if (lastSetVal) { return lastSetVal; } } } }); var map = new MyMap(); var oi = new Observation(function(){ return map.get("foo"); }); canReflect.onValue(oi, function(newVal){ assert.equal(newVal, "bar", "updated to bar"); }); map.set("foo","bar"); }); QUnit.test('Observation bound to async getter updates correctly (canjs#3541)', function(assert) { var MyMap = DefineMap.extend({ foo: { get: function(lastSetVal, resolve) { if (lastSetVal) { return resolve(lastSetVal); } } } }); var map = new MyMap(); var oi = new Observation(function(){ return map.get("foo"); }); canReflect.onValue(oi, function(newVal){ assert.equal(newVal, "bar", "updated to bar"); }); map.set("foo","bar"); }); canTestHelpers.devOnlyTest("log all property changes", function(assert) { var done = assert.async(); var Person = DefineMap.extend({ first: "string", last: "string", children: { Type: DefineList }, fullName: { get: function(){ return this.first + " " + this.last; } } }); var changed = []; var log = dev.log; dev.log = function() { // collect the property keys that were logged changed.push(JSON.parse(arguments[2])); }; var p = new Person(); p.log(); // bind fullName to get events from the getter p.on("fullName", function() {}); p.first = "Manuel"; p.last = "Mujica"; assert.expect(1); setTimeout(function() { dev.log = log; assert.deepEqual( changed, ["first", "fullName", "last", "fullName"], "should log all property changes" ); done(); }); }); canTestHelpers.devOnlyTest("log single property changes", function(assert) { var done = assert.async(); var Person = DefineMap.extend({ first: "string", last: "string", age: "number" }); var changed = []; var log = dev.log; dev.log = function() { // collect the property keys that were logged changed.push(JSON.parse(arguments[2])); }; var p = new Person(); p.log("first"); p.first = "John"; p.last = "Doe"; p.age = 99; assert.expect(1); setTimeout(function() { dev.log = log; assert.deepEqual(changed, ["first"], "should log 'first' changes"); done(); }); }); canTestHelpers.devOnlyTest("log multiple property changes", function(assert) { var done = assert.async(); var Person = DefineMap.extend({ first: "string", last: "string", age: "number", company: "string" }); var changed = []; var log = dev.log; dev.log = function() { // collect the property keys that were logged changed.push(JSON.parse(arguments[2])); }; var p = new Person(); p.log("first"); p.log("age"); p.first = "John"; p.last = "Doe"; p.company = "Bitovi"; p.age = 99; assert.expect(1); setTimeout(function() { dev.log = log; assert.deepEqual(changed, ["first", "age"], "should log first and age"); done(); }); }); canTestHelpers.devOnlyTest("Setting a value with an object type generates a warning (#148)", function(assert) { assert.expect(1); var message = "can-define: The default value for DefineMap{}.options is set to an object. This will be shared by all instances of the DefineMap. Use a function that returns the object instead."; var finishErrorCheck = canTestHelpers.willWarn(message); //should issue a warning DefineMap.extend({ options: { default: {} } }); //should issue a warning DefineMap.extend({ options: { default: [] } }); //should not issue a warning DefineMap.extend({ options: { default: function(){} } }); //should not issue a warning DefineMap.extend({ options: { default: 2 } }); assert.equal(finishErrorCheck(), 2); }); canTestHelpers.devOnlyTest("Setting a default value to a constructor type generates a warning", function(assert) { assert.expect(1); var message = "can-define: The \"default\" for DefineMap{}.options is set to a constructor. Did you mean \"Default\" instead?"; var finishErrorCheck = canTestHelpers.willWarn(message); //should issue a warning DefineMap.extend({ options: { default: DefineMap } }); assert.equal(finishErrorCheck(), 1); }); canTestHelpers.devOnlyTest("can.getName symbol behavior", function(assert) { var getName = function(instance) { return instance[canSymbol.for("can.getName")](); }; assert.ok( "DefineMap{}", getName(new DefineMap()), "should use DefineMap constructor name by default" ); var MyMap = DefineMap.extend("MyMap", {}); assert.ok( "MyMap{}", getName(new MyMap()), "should use custom map name when provided" ); }); canTestHelpers.devOnlyTest("Error on not using a constructor or string on short-hand definitions (#278)", function(assert) { assert.expect(5); var message = /does not match a supported propDefinition. See: https:\/\/canjs.com\/doc\/can-define.types.propDefinition.html/i; var finishErrorCheck = canTestHelpers.willError(message, function(actual, match) { var rightProp = /prop0[15]/; assert.ok(rightProp.test(actual.split(" ")[0])); assert.ok(match); }); DefineMap.extend('ShortName', { prop01: 0, prop02: function() {}, prop03: 'string', prop04: DefineMap, prop05: "a string that is not a type", prop06: [], get prop07() {}, set prop07(newVal) {}, prop08: 'boolean' }); assert.equal(finishErrorCheck(), 2); }); QUnit.test('Improper shorthand properties are not set', function(assert) { var VM = DefineMap.extend({ prop01: 0, prop02: function() {}, prop03: 'some random string' }); assert.equal(VM.prototype._define.methods.prop01, undefined); assert.equal(typeof VM.prototype._define.methods.prop02, 'function'); assert.equal(VM.prototype._define.methods.prop03, undefined); }); QUnit.test("onKeyValue sets up computed values", function(assert) { var fullNameCalls = []; var VM = DefineMap.extend({ first: "string", last: "string", get fullName() { fullNameCalls.push(this.first + " "+ this.last); return this.first + " "+ this.last; } }); var vm = new VM({first: "J", last: "M"}); canReflect.onKeyValue(vm, "fullName", function(){}); assert.deepEqual(fullNameCalls,["J M"]); }); QUnit.test("async getters derived from other properties should have correct keyDependencies", function(assert) { var VM = DefineMap.extend({ get source() { return 'source value'; }, derived: { get: function(last, resolve) { return resolve(this.source); } } }); var vm = new VM(); vm.on('derived', function(){}); assert.ok(vm._computed.derived.compute.observation.newDependencies.keyDependencies.get(vm).has('source'), 'getter should depend on vm.source'); }); var sealDoesErrorWithPropertyName = (function () { 'use strict'; var o = Object.seal({}); try { o.foo = "bar"; } catch (error) { return error.message.indexOf('foo') !== -1; } return false; })(); canTestHelpers.devOnlyTest("setting a property gives a nice error", function(assert){ var VM = DefineMap.extend({}); var vm = new VM(); try { vm.set("fooxyz","bar"); } catch (error) { if (sealDoesErrorWithPropertyName) { assert.ok( error.message.indexOf("fooxyz") !== -1, "Set property error with property name should be thrown" ); } else { assert.ok(true, 'Set property error should be thrown'); } } }); canTestHelpers.devOnlyTest("can.hasKey and can.hasOwnKey (#303) (#412)", function(assert) { var hasKeySymbol = canSymbol.for("can.hasKey"), hasOwnKeySymbol = canSymbol.for("can.hasOwnKey"); var Parent = DefineMap.extend({ parentProp: "any", get parentDerivedProp() { if (this.parentProp) { return "parentDerived"; } } }); var VM = Parent.extend({ prop: "any", get derivedProp() { if (this.prop) { return "derived"; } } }); var vm = new VM(); // hasKey assert.equal(vm[hasKeySymbol]("prop"), true, "vm.hasKey('prop') true"); assert.equal(vm[hasKeySymbol]("derivedProp"), true, "vm.hasKey('derivedProp') true"); assert.equal(vm[hasKeySymbol]("parentProp"), true, "vm.hasKey('parentProp') true"); assert.equal(vm[hasKeySymbol]("parentDerivedProp"), true, "vm.hasKey('parentDerivedProp') true"); assert.equal(vm[hasKeySymbol]("anotherProp"), false, "vm.hasKey('anotherProp') false"); // hasOwnKey assert.equal(vm[hasOwnKeySymbol]("prop"), true, "vm.hasOwnKey('prop') true"); assert.equal(vm[hasOwnKeySymbol]("derivedProp"), true, "vm.hasOwnKey('derivedProp') true"); assert.equal(vm[hasOwnKeySymbol]("parentProp"), false, "vm.hasOwnKey('parentProp') false"); assert.equal(vm[hasOwnKeySymbol]("parentDerivedProp"), false, "vm.hasOwnKey('parentDerivedProp') false"); assert.equal(vm[hasOwnKeySymbol]("anotherProp"), false, "vm.hasOwnKey('anotherProp') false"); var map = new DefineMap({expandoKey: undefined}); assert.equal(map[hasKeySymbol]("expandoKey"), true, "map.hasKey('expandoKey') (#412)"); }); canTestHelpers.devOnlyTest("getOwnKeys, getOwnEnumerableKeys (#326)", function(assert) { var getOwnEnumerableKeysSymbol = canSymbol.for("can.getOwnEnumerableKeys"), getOwnKeysSymbol = canSymbol.for("can.getOwnKeys"); var Parent = DefineMap.extend({ parentProp: "any", get parentDerivedProp() { if (this.parentProp) { return "parentDerived"; } }, parentValueProp: { value: function(prop) { if (this.parentProp) { prop.resolve(this.parentProp); } prop.listenTo("parentProp", prop.resolve); } } }); var VM = Parent.extend({ prop: "any", get derivedProp() { if (this.prop) { return "derived"; } }, valueProp: { value: function(prop) { if (this.prop) { prop.resolve(this.prop); } prop.listenTo("prop", prop.resolve); } } }); var vm = new VM(); // getOwnEnumerableKeys assert.deepEqual( vm[getOwnEnumerableKeysSymbol](), [ "prop", "valueProp", "parentProp", "parentValueProp" ], "vm.getOwnEnumerableKeys()"); // getOwnKeys assert.deepEqual( vm[getOwnKeysSymbol](), [ "prop", "valueProp", "parentProp", "parentValueProp", "derivedProp", "parentDerivedProp" ], "vm.getOwnKeys()"); }); QUnit.test("value as a string breaks", function(assert) { var MyMap = DefineMap.extend({ prop: {value: "a string"} }); var my = new MyMap(); assert.equal(my.prop, "a string", "works"); }); QUnit.test("canReflect.getSchema", function(assert) { // For #401 var StringIgnoreCase = canReflect.assignSymbols({},{ "can.new": function(value){ return value.toLowerCase(); } }); var MyType = DefineMap.extend({ id: {identity: true, type: "number"}, name: "string", foo: {serialize: false}, lowerCase: StringIgnoreCase, text: MaybeString, maybeString_type: {type: MaybeString}, maybeString_Type: {Type: MaybeString} }); var schema = canReflect.getSchema(MyType); assert.deepEqual(schema.identity, ["id"], "right identity"); assert.deepEqual(Object.keys(schema.keys), ["id","name","lowerCase","text","maybeString_type","maybeString_Type"], "right key names"); assert.equal( canReflect.convert("1", schema.keys.id), 1, "converted to number"); assert.equal( canReflect.convert(3, schema.keys.id), "3", "converted to number"); assert.equal(schema.keys.name, MaybeString, " 'string' -> MaybeString"); assert.equal(schema.keys.lowerCase, StringIgnoreCase, "StringIgnoreCase"); assert.equal(schema.keys.text, MaybeString, "MaybeString"); assert.equal(schema.keys.maybeString_type, MaybeString, "{type: MaybeString}"); assert.equal(schema.keys.maybeString_Type, MaybeString, "{Type: MaybeString}"); }); QUnit.test("use can.new and can.serialize for conversion", function(assert) { var Status = canReflect.assignSymbols({},{ "can.new": function(val){ return val.toLowerCase(); }, "can.getSchema": function(){ return { type: "Or", values: ["new","assigned","complete"] }; }, "can.serialize": function(){ return this.toUpperCase(); } }); var Todo = DefineMap.extend("Todo",{ status: Status }); var todo = new Todo({status: "NEW"}); assert.equal(todo.status, "new", "converted during set"); assert.deepEqual(todo.serialize(),{status: "NEW"}, "serialized to upper case"); var Todo2 = DefineMap.extend("Todo",{ due: "date" }); var date = new Date(2018,3,30); var todo2 = new Todo2({ due: date.toString() }); assert.ok(todo2.due instanceof Date, "converted to a date instance"); var res = todo2.serialize(); assert.deepEqual(res,{due: date}, "serialized to a date?"); }); QUnit.test("make sure stringOrObservable works", function(assert) { var Type = DefineMap.extend({ val : "stringOrObservable" }); var type = new Type({val: "foo"}); assert.equal(type.val, "foo", "works"); }); QUnit.test("primitive types work with val: Type", function(assert) { var UpperCase = {}; UpperCase[canSymbol.for("can.new")] = function(val){ return val.toUpperCase(); }; var Type = DefineMap.extend({ val: UpperCase }); var type = new Type({ val: "works" }); assert.equal(type.val, "WORKS", "it worked"); }); QUnit.test("primitive types work with val: {Type: Type}", function(assert) { var UpperCase = {}; UpperCase[canSymbol.for("can.new")] = function(val){ return val.toUpperCase(); }; var Type = DefineMap.extend({ val: { Type: UpperCase } }); var type = new Type({ val: "works" }); assert.equal(type.val, "WORKS", "it worked"); }); QUnit.test("primitive types work with val: {type: Type}", function(assert) { var UpperCase = {}; UpperCase[canSymbol.for("can.new")] = function(val){ return val.toUpperCase(); }; var Type = DefineMap.extend({ val: { type: UpperCase } }); var type = new Type({ val: "works" }); assert.equal(type.val, "WORKS", "it worked"); }); QUnit.test("ownKeys works on basic DefineMaps", function(assert) { var map = new DefineMap({ first: "Jane", last: "Doe" }); var keys = canReflect.getOwnKeys(map); assert.equal(keys.length, 2, "There are 2 keys"); }); QUnit.test("deleteKey works (#351)", function(assert) { var map = new DefineMap({foo: "bar"}); assert.deepEqual( canReflect.getOwnKeys(map), ["foo"] ); map.set("zed", "ted"); assert.deepEqual( canReflect.getOwnKeys(map), ["foo","zed"] ); map.deleteKey("zed"); assert.deepEqual( canReflect.getOwnKeys(map), ["foo"] ); map.deleteKey("foo"); // We should keep the property descriptor // var pd = Object.getOwnPropertyDescriptor(map, "foo"); // assert.ok(!pd, "no property descriptor"); assert.deepEqual( canReflect.getOwnKeys(map), [] ); map.set("foo", "bar"); // With sealed map = new DefineMap({foo: "bar"}, true); map.deleteKey("foo"); assert.equal(map.foo, undefined, "prop set to undefined"); }); QUnit.test("makes sure observation add is called (#393)", function(assert) { var map = new DefineMap({foo: "bar"}); canReflect.deleteKeyValue(map, "foo"); ObservationRecorder.start(); (function(){ return map.foo; }()); var result = ObservationRecorder.stop(); assert.deepEqual(canReflect.toArray( result.keyDependencies.get(map) ), ["foo"], "toArray" ); }); QUnit.test("type called with `this` as the map (#349)", function(assert) { var Type = DefineMap.extend({ foo: { type: function(){ assert.equal(Type, this.constructor, "got the right this"); return 5; }, default: 4 } }); var map = new Type(); assert.equal(map.foo, 5); }); QUnit.test("expandos use default type (#383)", function(assert) { var AllNumbers = DefineMap.extend({ "*": {type: "number"} }); var someNumbers = new AllNumbers({ version: "24" }); assert.ok(someNumbers.version === 24, "is 24"); }); QUnit.test("do not enumerate anything other than key properties (#369)", function(assert) { // Internet Explorer doesn't correctly skip properties that are non-enumerable // on the current object, but enumerable on the prototype: var ancestor = { prop: true }; var F = function() {}; F.prototype = ancestor; var descendant = new F(); Object.defineProperty(descendant, "prop", { writable: true, configurable: true, enumerable: false, value: true }); var test = {}; for (var k in descendant) { test[k] = descendant[k]; } if (test.prop) { return assert.ok(test.prop, "Browser doesn't correctly skip shadowed enumerable properties"); } var Type = DefineMap.extend({ aProp: "string", aMethod: function(){} }); var instance = new Type({aProp: "VALUE", anExpando: "VALUE"}); var props = {}; for (var prop in instance) { props[prop] = true; } assert.deepEqual(props,{ aProp: true, anExpando: true, aMethod: true // TODO: this should be removed someday }); }); QUnit.test("Properties added via defineInstanceKey are observable", function(assert) { var Type = DefineMap.extend({}); var map = new Type(); var obs = new Observation(function(){ return canReflect.serialize(map); }); var count = 0; canReflect.onValue(obs, function(val) { count++; if(count === 2) { assert.deepEqual(val, {foo:"bar"}, "changed value"); } }); canReflect.defineInstanceKey(Type, "foo", { type: "string" }); map.foo = "bar"; }); QUnit.test("Serialized computes do not prevent getters from working", function(assert) { var Type = DefineMap.extend("MyType", { page: "string", myPage: { get: function(last, resolve) { return this.page; } } }); var first = new Type({ page: "one" }); var firstObservation = new Observation(function() { return canReflect.serialize(first); }); var boundTo = Function.prototype; canReflect.onValue(firstObservation, boundTo); var second = new Type({ page: "two" }); assert.equal(second.myPage, "two", "Runs the getter correctly"); }); QUnit.test("setup should be called (#395)", function(assert) { var calls = []; var Base = DefineMap.extend("Base",{ setup: function(attrs) { calls.push(this); return DefineMap.prototype.setup.apply(this, arguments); } }); var Super = Base.extend("Super",{}); var base = new Base(); var supa = new Super(); assert.deepEqual(calls,[base, supa], "setup called"); }); QUnit.test("Set new prop to undefined #408", function(assert) { var obj = new DefineMap({}); var PATCHES = [ [ { type: "add", key: "foo", value: undefined } ], [ { type: "set", key: "foo", value: "bar" } ] ]; var calledPatches = []; var handler = function(patches){ calledPatches.push(patches); }; obj[canSymbol.for("can.onPatches")](handler,"notify"); obj.set("foo", undefined); obj.set("foo", "bar"); assert.deepEqual(calledPatches, PATCHES); }); QUnit.test("Set __inSetup prop #421", function(assert) { var map = new DefineMap({}); map.set("__inSetup", "nope"); assert.equal(map.__inSetup, "nope"); }); QUnit.test("'*' wildcard type definitions that use constructors works for expandos #425", function(assert) { var MyType = function MyType() {}; MyType.prototype = {}; var OtherType = DefineMap.extend({ seal : false }, { "*" : MyType }); var map = new OtherType(); map.set( "foo", {}); var foo = map.get( "foo" ); assert.ok(foo instanceof MyType); }); QUnit.test("'*' wildcard type definitions that use DefineMap constructors works for expandos #425", function(assert) { var MyType = DefineMap.extend({}); var OtherType = DefineMap.extend({ seal : false }, { "*" : MyType }); var map = new OtherType(); map.set( "foo", {}); var foo = map.get( "foo" ); assert.ok(foo instanceof MyType); }); require("can-reflect-tests/observables/map-like/instance/on-event-get-set-delete-key")("DefineMap", function(){ return new DefineMap(); });