UNPKG

can-define

Version:

Create observable objects with JS dot operator compatibility

1,534 lines (1,217 loc) 31.3 kB
var QUnit = require("steal-qunit"); var define = require("can-define"); var queues = require("can-queues"); var canSymbol = require("can-symbol"); var SimpleObservable = require("can-simple-observable"); var testHelpers = require("can-test-helpers"); var canReflect = require("can-reflect"); var ObservationRecorder = require("can-observation-recorder"); QUnit.module("can-define"); QUnit.test("basics on a prototype", function(assert) { assert.expect(5); var Person = function(first, last) { this.first = first; this.last = last; }; define(Person.prototype, { first: "*", last: "*", fullName: { get: function() { return this.first + " " + this.last; } } }); var p = new Person("Mohamed", "Cherif"); p.bind("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.bind("first", function(el, newVal, oldVal) { assert.equal(newVal, "Justin", "first new value"); assert.equal(oldVal, "Mohamed", "first old value"); }); queues.batch.start(); p.first = "Justin"; p.last = "Meyer"; queues.batch.stop(); }); QUnit.test('basics set', function(assert) { assert.expect(2); var Defined = function(prop) { this.prop = prop; }; define(Defined.prototype, { prop: { set: function(newVal) { return "foo" + newVal; } } }); var def = new Defined(); def.prop = "bar"; assert.equal(def.prop, "foobar", "setter works"); var DefinedCB = function(prop) { this.prop = prop; }; define(DefinedCB.prototype, { prop: { set: function(newVal, setter) { setter("foo" + newVal); } } }); var defCallback = new DefinedCB(); defCallback.prop = "bar"; assert.equal(defCallback.prop, "foobar", "setter callback works"); }); QUnit.test("basic Type", function(assert) { var Foo = function(name) { this.name = name; }; Foo.prototype.getName = function() { return this.name; }; var Typer = function(foo) { this.foo = foo; }; define(Typer.prototype, { foo: { Type: Foo } }); var t = new Typer("Justin"); assert.equal(t.foo.getName(), "Justin", "correctly created an instance"); var brian = new Foo("brian"); t.foo = brian; assert.equal(t.foo, brian, "same instances"); }); QUnit.test("type converters", function(assert) { var Typer = function(date, string, number, bool, htmlbool, leaveAlone) { this.date = date; this.string = string; this.number = number; this.bool = bool; this.htmlbool = htmlbool; this.leaveAlone = leaveAlone; }; define(Typer.prototype, { date: { type: 'date' }, string: { type: 'string' }, number: { type: 'number' }, bool: { type: 'boolean' }, htmlbool: { type: 'htmlbool' }, leaveAlone: { type: '*' }, }); var obj = {}; var t = new Typer( 1395896701516, 5, '5', 'false', "", obj ); assert.ok(t.date instanceof Date, "converted to date"); assert.equal(t.string, '5', "converted to string"); assert.equal(t.number, 5, "converted to number"); assert.equal(t.bool, false, "converted to boolean"); assert.equal(t.htmlbool, true, "converted to htmlbool"); assert.equal(t.leaveAlone, obj, "left as object"); t.number = '15'; assert.ok(t.number === 15, "converted to number"); }); QUnit.test("basics value", function(assert) { var Typer = function(prop) { if (prop !== undefined) { this.prop = prop; } }; define(Typer.prototype, { prop: { default: 'foo' } }); var t = new Typer(); assert.equal(t.prop, "foo", "value is used as default value"); var Typer2 = function(prop) { if (prop !== undefined) { this.prop = prop; } }; define(Typer2.prototype, { prop: { default: function() { return []; }, type: "*" } }); var t1 = new Typer2(), t2 = new Typer2(); assert.ok(t1.prop !== t2.prop, "different array instances"); assert.ok(Array.isArray(t1.prop), "its an array"); }); QUnit.test("basics Value", function(assert) { var Typer = function(prop) { //this.prop = prop; }; define(Typer.prototype, { prop: { Default: Array, type: "*" } }); var t1 = new Typer(), t2 = new Typer(); assert.ok(t1.prop !== t2.prop, "different array instances"); assert.ok(Array.isArray(t1.prop), "its an array"); }); QUnit.test("setter with no arguments and returns undefined does the default behavior, the setter is for side effects only", function(assert) { var Typer = function(prop) { //this.prop = prop; }; define(Typer.prototype, { prop: { set: function() { this.foo = "bar"; } }, foo: "*" }); var t = new Typer(); t.prop = false; assert.deepEqual({ foo: t.foo, prop: t.prop }, { foo: "bar", prop: false }, "got the right props"); }); QUnit.test("type happens before the set", function(assert) { assert.expect(2); var Typer = function() {}; define(Typer.prototype, { prop: { type: "number", set: function(newValue) { assert.equal(typeof newValue, "number", "got a number"); return newValue + 1; } } }); var map = new Typer(); map.prop = "5"; assert.equal(map.prop, 6, "number"); }); QUnit.test("getter and setter work", function(assert) { assert.expect(5); var Paginate = define.Constructor({ limit: "*", offset: "*", page: { set: function(newVal) { this.offset = (parseInt(newVal) - 1) * this.limit; }, get: function() { return Math.floor(this.offset / this.limit) + 1; } } }); var p = new Paginate({ limit: 10, offset: 20 }); assert.equal(p.page, 3, "page get right"); p.bind("page", function(ev, newValue, oldValue) { assert.equal(newValue, 2, "got new value event"); assert.equal(oldValue, 3, "got old value event"); }); p.page = 2; assert.equal(p.page, 2, "page set right"); assert.equal(p.offset, 10, "page offset set"); }); QUnit.test("getter with initial value", function(assert) { var comp = new SimpleObservable(1); var Grabber = define.Constructor({ vals: { type: "*", Default: Array, get: function(current, setVal) { if (setVal) { current.push(comp.get()); } return current; } } }); var g = new Grabber(); // This assertion doesn't mean much. It's mostly testing // that there were no errors. assert.equal(g.vals.length, 0, "zero items in array"); }); /* test("value generator is not called if default passed", function () { var TestMap = define.Constructor({ foo: { value: function () { throw '"foo"\'s value method should not be called.'; } } }); var tm = new TestMap({ foo: 'baz' }); equal(tm.foo, 'baz'); });*/ QUnit.test('default behaviors with "*" work for attributes', function(assert) { assert.expect(6); var DefaultMap = define.Constructor({ '*': { type: 'number', set: function(newVal) { assert.ok(true, 'set called'); return newVal; } }, someNumber: { default: '5' }, number: {} }); var map = new DefaultMap(); assert.equal(map.someNumber, '5', 'default values are not type converted anymore'); map.someNumber = '5'; assert.equal(map.someNumber, 5, 'on a set, they should be type converted'); map.number = '10'; // Custom set should be called assert.equal(map.number, 10, 'value of number should be converted to a number'); }); QUnit.test("nested define", function(assert) { var nailedIt = 'Nailed it'; var Example = define.Constructor({ name: { default: nailedIt } }); var NestedMap = define.Constructor({ isEnabled: { default: true }, test: { Default: Example }, examples: { type: { one: { Default: Example }, two: { type: { deep: { Default: Example } }, Default: Object } }, Default: Object } }); var nested = new NestedMap(); // values are correct assert.equal(nested.test.name, nailedIt); assert.equal(nested.examples.one.name, nailedIt); assert.equal(nested.examples.two.deep.name, nailedIt); // objects are correctly instanced assert.ok(nested.test instanceof Example); assert.ok(nested.examples.one instanceof Example); assert.ok(nested.examples.two.deep instanceof Example); }); QUnit.test('Can make an attr alias a compute (#1470)', function(assert) { assert.expect(9); var computeValue = new SimpleObservable(1); var GetMap = define.Constructor({ value: { set: function(newValue, setVal, oldValue) { if (newValue instanceof SimpleObservable) { return newValue; } if (oldValue && (oldValue instanceof SimpleObservable)) { oldValue.set(newValue); return oldValue; } return newValue; }, get: function(value) { return value instanceof SimpleObservable ? value.get() : value; } } }); var getMap = new GetMap(); getMap.value = computeValue; assert.equal(getMap.value, 1, "initial value read from compute"); var bindCallbacks = 0; getMap.bind("value", function(ev, newVal, oldVal) { switch (bindCallbacks) { case 0: assert.equal(newVal, 2, "0 - bind called with new val"); assert.equal(oldVal, 1, "0 - bind called with old val"); break; case 1: assert.equal(newVal, 3, "1 - bind called with new val"); assert.equal(oldVal, 2, "1 - bind called with old val"); break; case 2: assert.equal(newVal, 4, "2 - bind called with new val"); assert.equal(oldVal, 3, "2 - bind called with old val"); break; } bindCallbacks++; }); // Try updating the compute's value computeValue.set(2); // Try setting the value of the property getMap.value = 3; assert.equal(getMap.value, 3, "read value is 3"); assert.equal(computeValue.get(), 3, "the compute value is 3"); // Try setting to a new comptue var newComputeValue = new SimpleObservable(4); getMap.value = newComputeValue; }); QUnit.test("One event on getters (#1585)", function(assert) { var Person = define.Constructor({ name: "*", id: "number" }); var AppState = define.Constructor({ person: { get: function appState_person_get(lastSetValue, resolve) { if (lastSetValue) { return lastSetValue; } else if (this.personId) { resolve(new Person({ name: "Jose", id: 5 })); } else { return null; } }, Type: Person }, personId: "*" }); var appState = new AppState(); var personEvents = 0; appState.bind("person", function addPersonEvents(ev, person) { personEvents++; }); assert.equal(appState.person, null, "no personId and no lastSetValue"); appState.personId = 5; assert.equal(appState.person.name, "Jose", "a personId, providing Jose"); assert.ok(appState.person instanceof Person, "got a person instance"); appState.person = { name: "Julia" }; assert.ok(appState.person instanceof Person, "got a person instance"); assert.equal(personEvents, 2); }); QUnit.test('Can read a defined property with a set/get method (#1648)', function(assert) { // Problem: "get" is not passed the correct "lastSetVal" // Problem: Cannot read the value of "foo" var Map = define.Constructor({ foo: { default: '', set: function(setVal) { return setVal; }, get: function(lastSetVal) { return lastSetVal; } } }); var map = new Map(); assert.equal(map.foo, '', 'Calling .foo returned the correct value'); map.foo = 'baz'; assert.equal(map.foo, 'baz', 'Calling .foo returned the correct value'); }); QUnit.test('Can bind to a defined property with a set/get method (#1648)', function(assert) { assert.expect(3); // Problem: "get" is not called before and after the "set" // Problem: Function bound to "foo" is not called // Problem: Cannot read the value of "foo" var Map = define.Constructor({ foo: { default: '', set: function(setVal) { return setVal; }, get: function(lastSetVal) { return lastSetVal; } } }); var map = new Map(); map.bind('foo', function() { assert.ok(true, 'Bound function is called'); }); assert.equal(map.foo, '', 'Calling .attr(\'foo\') returned the correct value'); map.foo = 'baz'; assert.equal(map.foo, 'baz', 'Calling .attr(\'foo\') returned the correct value'); }); QUnit.test("type converters handle null and undefined in expected ways (1693)", function(assert) { var Typer = define.Constructor({ date: { type: 'date' }, string: { type: 'string' }, number: { type: 'number' }, 'boolean': { type: 'boolean' }, htmlbool: { type: 'htmlbool' }, leaveAlone: { type: '*' } }); var t = new Typer({ date: undefined, string: undefined, number: undefined, 'boolean': undefined, htmlbool: undefined, leaveAlone: undefined }); assert.equal(t.date, undefined, "converted to date"); assert.equal(t.string, undefined, "converted to string"); assert.equal(t.number, undefined, "converted to number"); assert.equal(t.boolean, undefined, "converted to boolean"); //Updated for canjs#2316 assert.equal(t.htmlbool, false, "converted to htmlbool"); assert.equal(t.leaveAlone, undefined, "left as object"); t = new Typer({ date: null, string: null, number: null, 'boolean': null, htmlbool: null, leaveAlone: null }); assert.equal(t.date, null, "converted to date"); assert.equal(t.string, null, "converted to string"); assert.equal(t.number, null, "converted to number"); assert.equal(t.boolean, null, "converted to boolean"); //Updated for canjs#2316 assert.equal(t.htmlbool, false, "converted to htmlbool"); assert.equal(t.leaveAlone, null, "left as object"); }); QUnit.test('Initial value does not call getter', function(assert) { assert.expect(0); var Map = define.Constructor({ count: { get: function(lastVal) { assert.ok(false, 'Should not be called'); return lastVal; } } }); new Map({ count: 100 }); }); QUnit.test("getters produce change events", function(assert) { var Map = define.Constructor({ count: { get: function(lastVal) { return lastVal; } } }); var map = new Map(); // map.bind("change", function(){ // ok(true, "change called"); // }); map.bind('count', function() { assert.ok(true, "change called"); }); map.count = 22; }); QUnit.test("Asynchronous virtual properties cause extra recomputes (#1915)", function(assert) { var done = assert.async(); var ran = false; var VM = define.Constructor({ foo: { get: function(lastVal, setVal) { setTimeout(function() { if (setVal) { setVal(5); } }, 10); } }, bar: { get: function() { var foo = this.foo; if (foo) { if (ran) { assert.ok(false, 'Getter ran twice'); } ran = true; return foo * 2; } } } }); var vm = new VM(); vm.bind('bar', function() {}); setTimeout(function() { assert.equal(vm.bar, 10); done(); }, 200); }); QUnit.test('Default values cannot be set (#8)', function(assert) { var Person = function() {}; define(Person.prototype, { first: { type: 'string', default: 'Chris' }, last: { type: 'string', default: 'Gomez' }, fullName: { get: function() { return this.first + ' ' + this.last; } } }); var p = new Person(); assert.equal(p.fullName, 'Chris Gomez', 'Fullname is correct'); p.first = 'Sara'; assert.equal(p.fullName, 'Sara Gomez', 'Fullname is correct after update'); }); QUnit.test('default type is setable', function(assert) { var Person = function() {}; define(Person.prototype, { '*': 'string', first: { default: 1 }, last: { default: 2 } }); var p = new Person(); assert.ok(p.first === '1', typeof p.first); assert.ok(p.last === '2', typeof p.last); }); QUnit.test("expandos are added in define.setup (#25)", function(assert) { var MyMap = define.Constructor({}); var map = new MyMap({ prop: 4 }); map.on("prop", function() { assert.ok(true, "prop event called"); }); map.prop = 5; }); QUnit.test('Set property with type compute', function(assert) { var MyMap = define.Constructor({ computeProp: { type: 'compute' } }); var m = new MyMap(); m.computeProp = new SimpleObservable(0); assert.equal(m.computeProp, 0, 'Property has correct value'); m.computeProp = new SimpleObservable(1); assert.equal(m.computeProp, 1, 'Property has correct value'); }); QUnit.test('Compute type property can have a default value', function(assert) { var MyMap = define.Constructor({ computeProp: { type: 'compute', default: function() { return 0; } } }); var m = new MyMap(); assert.equal(m.computeProp, 0, 'Property has correct value'); m.computeProp = 1; assert.equal(m.computeProp, 1, 'Property has correct value'); }); QUnit.test('Compute type property with compute default value triggers change events when updated', function(assert) { var expected = 0; var c = new SimpleObservable(0); var MyMap = define.Constructor({ computeProp: { type: 'compute', default: function() { return c; } } }); var m = new MyMap(); c.on(function(newVal) { assert.equal(newVal, expected, 'Compute fired change event'); }); m.on('computeProp', function(ev, newVal) { assert.equal(newVal, expected, 'Map fired change event'); }); expected = 1; m.computeProp = expected; expected = 2; c.set(expected); }); QUnit.test('Compute type property can have a default value that is a compute', function(assert) { var c = new SimpleObservable(0); var MyMap = define.Constructor({ computeProp: { type: 'compute', default: function() { return c; } } }); var m = new MyMap(); assert.equal(m.computeProp, 0, 'Property has correct value'); c.set(1); assert.equal(m.computeProp, 1, 'Property has correct value'); }); QUnit.test('Extensions can modify definitions', function(assert) { var oldExtensions = define.extensions; define.behaviors.push('extended'); define.extensions = function(objPrototype, prop, definition) { if (definition.extended) { return { default: 'extended' }; } }; var MyMap = define.Constructor({ foo: { default: 'defined', extended: true, }, bar: { default: 'defined' } }); var map = new MyMap(); assert.equal(map.foo, 'extended', 'Value was set via extension'); assert.equal(map.bar, 'defined', 'Value was set via definition'); define.extensions = oldExtensions; }); QUnit.test("Properties are enumerable", function(assert) { assert.expect(1); function VM(foo) { this.foo = foo; } define(VM.prototype, { foo: "string" }); var vm = new VM("bar"); vm.baz = "qux"; var copy = {}; for(var key in vm) { copy[key] = vm[key]; } assert.deepEqual(copy,{ foo: "bar", baz: "qux" }); }); QUnit.test("Doesn't override canSymbol.iterator if already on the prototype", function(assert) { function MyMap() {} MyMap.prototype[canSymbol.iterator || canSymbol.for("iterator")] = function() { var i = 0; return { next: function() { if (i === 0) { i++; return { value: ["it", "worked"], done: false }; } return { value: undefined, done: true }; } }; }; define(MyMap.prototype, { foo: "string" }); var map = new MyMap(); map.foo = "bar"; canReflect.eachIndex(map, function(value) { assert.deepEqual(value, ["it","worked"]); }); }); QUnit.test("nullish values are not converted for type or Type", function(assert) { var Foo = function() {}; var MyMap = define.Constructor({ map: { Type: Foo }, notype: {} }); var vm = new MyMap({ map: {}, notype: {} }); // Sanity check assert.ok(vm.map instanceof Foo, "map is another type"); assert.ok(vm.notype instanceof Object, "notype is an Object"); vm.map = null; vm.notype = null; assert.equal(vm.map, null, "map is null"); assert.equal(vm.map, null, "notype is null"); }); QUnit.test("shorthand getter (#56)", function(assert) { var Person = function(first, last) { this.first = first; this.last = last; }; define(Person.prototype, { first: "*", last: "*", get fullName() { return this.first + " " + this.last; } }); var p = new Person("Mohamed", "Cherif"); p.on("fullName", function(ev, newVal, oldVal) { assert.equal(oldVal, "Mohamed Cherif"); assert.equal(newVal, "Justin Meyer"); }); assert.equal(p.fullName, "Mohamed Cherif", "fullName initialized right"); queues.batch.start(); p.first = "Justin"; p.last = "Meyer"; queues.batch.stop(); }); QUnit.test("shorthand getter setter (#56)", function(assert) { var Person = function(first, last) { this.first = first; this.last = last; }; define(Person.prototype, { 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("Mohamed", "Cherif"); p.on("fullName", function(ev, newVal, oldVal) { assert.equal(oldVal, "Mohamed Cherif"); assert.equal(newVal, "Justin Meyer"); }); assert.equal(p.fullName, "Mohamed Cherif", "fullName initialized right"); p.fullName = "Justin Meyer"; }); QUnit.test("set and value work together (#87)", function(assert) { var Type = define.Constructor({ prop: { default: 2, set: function(num){ return num * num; } } }); var instance = new Type(); assert.equal(instance.prop, 4, "used setter"); }); QUnit.test("async setter is provided", function(assert) { assert.expect(5); var RESOLVE; var Type = define.Constructor({ prop: { default: 2, set: function(num, resolve){ resolve( num * num ); } }, prop2: { default: 3, set: function(num, resolve){ RESOLVE = resolve; } } }); var instance = new Type(); assert.equal(instance.prop, 4, "used async setter"); assert.equal(instance.prop2, undefined, "used async setter"); instance.on("prop2", function(ev, newVal, oldVal){ assert.equal(newVal, 9, "updated"); assert.equal(oldVal, undefined, "updated"); }); RESOLVE(9); assert.equal(instance.prop2, 9, "used async setter updates after"); }); QUnit.test('setter with default value causes an infinite loop (#142)', function(assert) { var A = define.Constructor({ val: { default: 'hello', set: function(val){ if(this.val) {} return val; } } }); var a = new A(); assert.equal(a.val, 'hello', 'creating an instance should not cause an inifinte loop'); }); QUnit.test('defined properties are configurable', function(assert) { var A = define.Constructor({ val: { get: function(){ return "foo"; } } }); var dataInitializers = A.prototype._define.dataInitializers, computedInitializers = A.prototype._define.computedInitializers; var newDefinition = { get: function(){ return "bar"; } }; define.property(A.prototype, "val", newDefinition, dataInitializers, computedInitializers); var a = new A(); assert.equal(a.val, "bar", "It was redefined"); }); testHelpers.dev.devOnlyTest("warn on using a Constructor for small-t type definitions", function (assert) { assert.expect(1); var message = /can-define: the definition for [\w{}\.]+ uses a constructor for "type"\. Did you mean "Type"\?/; var finishErrorCheck = testHelpers.dev.willWarn(message); function Currency() { return this; } Currency.prototype = { symbol: "USD" }; function VM() {} define(VM.prototype, { currency: { type: Currency, // should be `Type: Currency` default: function() { return new Currency({}); } } }); assert.equal(finishErrorCheck(), 1); }); testHelpers.dev.devOnlyTest("warn with constructor for Value instead of Default (#340)", function (assert) { assert.expect(1); var message = /can-define: Change the 'Value' definition for [\w\.{}]+.currency to 'Default'./; var finishErrorCheck = testHelpers.dev.willWarn(message); function Currency() { return this; } Currency.prototype = { symbol: "USD" }; function VM() {} define(VM.prototype, { currency: { Value: Currency } }); assert.equal(finishErrorCheck(), 1); }); QUnit.test("canReflect.onKeyValue (#363)", function(assert) { var Greeting = function( message ) { this.message = message; }; define( Greeting.prototype, { message: { type: "string" } } ); var greeting = new Greeting("Hello"); canReflect.onKeyValue(greeting, "message", function(newVal, oldVal) { assert.equal(newVal, "bye"); assert.equal(oldVal, "Hello"); }); greeting.message = "bye"; }); QUnit.test("value lastSet has default value (#397)", function(assert) { var Defaulted = function() {}; define(Defaulted.prototype, { hasDefault: { default: 42, value: function hasDefaultValue(props) { assert.equal(props.lastSet.get(), 42, "props.lastSet works"); props.resolve(props.lastSet.get()); } } }); var defaulted = new Defaulted(); assert.equal(defaulted.hasDefault, 42, "hasDefault value.lastSet set default value"); }); QUnit.test("binding computed properties do not observation recordings (#406)", function(assert) { var Type = function() {}; define(Type.prototype, { prop: { get: function(){ return "foo"; } } }); var inst = new Type(); ObservationRecorder.start(); inst.on("prop", function(){}); var records = ObservationRecorder.stop(); assert.equal(records.valueDependencies.size, 0, "nothing recorded"); }); testHelpers.dev.devOnlyTest("warning when setting during a get", function(assert){ var Type = function Type() {}; var msg = /.* This can cause infinite loops and performance issues.*/; var teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(true, "warning fired"); } }); define(Type.prototype, { prop: { get: function(){ if(!this.prop2) { this.prop2 = "baz"; } return ""; } }, prop2: "string" }); var inst = new Type(); inst.on("prop", function(){}); inst.prop2 = ""; assert.equal(teardownWarn(), 1, "warning correctly generated"); teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(false, "warning incorrectly fired"); } }); inst.prop2 = "quux"; teardownWarn(); }); testHelpers.dev.devOnlyTest("warning when setting during a get (batched)", function(assert){ var msg = /.* This can cause infinite loops and performance issues.*/; var Type = function Type() {}; var teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(true, "warning fired"); } }); define(Type.prototype, { prop: { get: function(){ if(!this.prop2) { this.prop2 = "baz"; return ""; } } }, prop2: "string" }); var inst = new Type(); queues.batch.start(); inst.on("prop", function(){}); inst.prop2 = ""; queues.batch.stop(); assert.equal(teardownWarn(), 1, "warning correctly generated"); teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(false, "warning incorrectly fired"); } }); queues.batch.start(); inst.prop2 = "quux"; queues.batch.stop(); teardownWarn(); }); testHelpers.dev.devOnlyTest("warning when setting during a get (setter)", function(assert){ var msg = /.* This can cause infinite loops and performance issues.*/; var Type = function Type() {}; var teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(true, "warning fired"); } }); var cell; define(Type.prototype, { prop: { get: function() { if(!this.prop2) { this.prop2 = "baz"; } return cell; }, set: function(val) { cell = val; } }, prop2: "string" }); var inst = new Type(); inst.on("prop", function(){}); // generates a warning inst.prop2 = ""; // also generates a warning, as the bound getter will fire again assert.equal(teardownWarn(), 1, "warning correctly generated"); teardownWarn = testHelpers.dev.willWarn(msg, function(text, match) { if(match) { assert.ok(false, "warning incorrectly fired"); } }); inst.prop2 = "quux"; teardownWarn(); }); testHelpers.dev.devOnlyTest("warnings are given when type or default is ignored", function(assert) { var testCases = [ { name: "zero-arg getter, no setter when property is set", definition: { get: function() { return "whatever"; } }, warning: /Set value for property .* ignored/, setProp: true, expectedWarnings: 1 }, { name: "type with zero-arg getter, no setter", definition: { type: String, get: function() { return "whatever"; } }, warning: /type value for property .* ignored/, setProp: false, expectedWarnings: 1 }, { name: "Type with zero-arg getter, no setter", definition: { Type: {}, get: function() { return "whatever"; } }, warning: /Type value for property .* ignored/, setProp: false, expectedWarnings: 1 }, { name: "only default type with zero-arg getter, no setter - should not warn", definition: { get: function() { return "whatever"; } }, warning: /type value for property .* ignored/, setProp: false, expectedWarnings: 0 }, { name: "type with zero-arg getter, with setter - should not warn", definition: { type: String, get: function() { return "whatever"; }, set: function (val) { return val; } }, warning: /type value for property .* ignored/, setProp: false, expectedWarnings: 0 }, { name: "Type with zero-arg getter, with setter - should not warn", definition: { Type: {}, get: function() { return "whatever"; }, set: function (val) { return val; } }, warning: /Type value for property .* ignored/, setProp: false, expectedWarnings: 0 }, { name: "default with zero-arg getter, no setter", definition: { default: "some thing", get: function() { return "whatever"; } }, warning: /default value for property .* ignored/, setProp: false, expectedWarnings: 1 }, { name: "Default with zero-arg getter, no setter", definition: { Default: function () {}, get: function() { return "whatever"; } }, warning: /Default value for property .* ignored/, setProp: false, expectedWarnings: 1 }, { name: "default with zero-arg getter, with setter - should not warn", definition: { default: "some thing", get: function() { return "whatever"; }, set: function (val) { return val; } }, warning: /default value for property .* ignored/, setProp: false, expectedWarnings: 0 }, { name: "Default with zero-arg getter, with setter - should not warn", definition: { Default: function () {}, get: function() { return "whatever"; }, set: function (val) { return val; } }, warning: /Default value for property .* ignored/, setProp: false, expectedWarnings: 0 } ]; testCases.forEach(function(testCase) { var VM = function() {}; var warnCount = testHelpers.dev.willWarn(testCase.warning); define(VM.prototype, { derivedProp: testCase.definition, "*": { // emulates can-define/map/map setting default type type: define.types.observable } }); var vm = new VM(); // read prop for 'lazy' setup canReflect.onKeyValue(vm, 'derivedProp', function() {}); if (testCase.setProp) { vm.derivedProp = "smashed it!"; } assert.equal(warnCount(), testCase.expectedWarnings, "got correct number of warnings for " + testCase.name); }); });