ampersand-state
Advanced tools
Comparing version 4.3.0 to 4.3.1
{ | ||
"name": "ampersand-state", | ||
"description": "An observable, extensible state object with derived watchable properties.", | ||
"version": "4.3.0", | ||
"version": "4.3.1", | ||
"author": "Henrik Joreteg <henrik@andyet.net>", | ||
@@ -6,0 +6,0 @@ "bugs": { |
@@ -274,1 +274,701 @@ /*jshint expr: true*/ | ||
}); | ||
test("isNew", function (t) { | ||
var Foo = State.extend({ | ||
props: { | ||
id: 'number', | ||
foo: 'number', | ||
bar: 'number', | ||
baz: 'number' | ||
} | ||
}); | ||
var a = new Foo({ 'foo': 1, 'bar': 2, 'baz': 3}); | ||
t.ok(a.isNew(), "it should be new"); | ||
a = new Foo({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 }); | ||
t.ok(!a.isNew(), "any defined ID is legal, negative or positive"); | ||
a = new Foo({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 }); | ||
t.ok(!a.isNew(), "any defined ID is legal, including zero"); | ||
t.ok(new Foo({}).isNew(), "is true when there is no id"); | ||
t.ok(!new Foo({'id': 2}).isNew(), "is false for a positive integer"); | ||
t.ok(!new Foo({'id': -5}).isNew(), "is false for a negative integer"); | ||
t.end(); | ||
}); | ||
test("escape", function (t) { | ||
var Doc = State.extend({ | ||
props: { | ||
id: 'string', | ||
title: 'string', | ||
author: 'string', | ||
length: 'number', | ||
audience: 'string' | ||
} | ||
}); | ||
var doc = new Doc({ | ||
id: '1-the-tempest', | ||
title: "The Tempest", | ||
author: "Bill Shakespeare", | ||
length: 123 | ||
}); | ||
t.equal(doc.escape('title'), 'The Tempest'); | ||
doc.set({audience: 'Bill & Bob'}); | ||
t.equal(doc.escape('audience'), 'Bill & Bob'); | ||
doc.set({audience: 'Tim > Joan'}); | ||
t.equal(doc.escape('audience'), 'Tim > Joan'); | ||
doc.unset('audience'); | ||
t.equal(doc.escape('audience'), ''); | ||
t.end(); | ||
}); | ||
test("set an empty string", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
name: 'string' | ||
} | ||
}); | ||
var model = new Model({name : "Model"}); | ||
model.set({name : ''}); | ||
t.equal(model.get('name'), ''); | ||
t.end(); | ||
}); | ||
test("setting an object", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
custom: 'object' | ||
} | ||
}); | ||
var model = new Model({ | ||
custom: {foo: 1} | ||
}); | ||
model.on('change', function () { | ||
t.equal(model.custom.foo, 2); | ||
t.end(); | ||
}); | ||
model.set({ | ||
custom: {foo: 1} // no change should be fired | ||
}); | ||
model.set({ | ||
custom: {foo: 2} // change event should be fired | ||
}); | ||
}); | ||
test("clear", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
name: 'string', | ||
id: 'number' | ||
} | ||
}); | ||
var changed; | ||
var model = new Model({id: 1, name : "Model"}); | ||
model.on("change:name", function () { changed = true; }); | ||
model.clear(); | ||
t.equal(changed, true); | ||
t.equal(model.get('name'), undefined); | ||
t.end(); | ||
}); | ||
test("changedAttributes", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
a: 'string', | ||
b: 'string' | ||
} | ||
}); | ||
var model = new Model({a: 'a', b: 'b'}); | ||
t.deepEqual(model.changedAttributes(), false); | ||
t.equal(model.changedAttributes({a: 'a'}), false); | ||
t.equal(model.changedAttributes({a: 'b'}).a, 'b'); | ||
t.end(); | ||
}); | ||
test("change with options", function (t) { | ||
var value; | ||
var Model = State.extend({ | ||
props: { | ||
name: 'string' | ||
} | ||
}); | ||
var model = new Model({name: 'Rob'}); | ||
model.on('change', function (model, options) { | ||
value = options.prefix + model.get('name'); | ||
}); | ||
model.set({name: 'Bob'}, {prefix: 'Mr. '}); | ||
t.equal(value, 'Mr. Bob'); | ||
model.set({name: 'Sue'}, {prefix: 'Ms. '}); | ||
t.equal(value, 'Ms. Sue'); | ||
t.end(); | ||
}); | ||
test("change after initialize", function (t) { | ||
var changed = 0; | ||
var Model = State.extend({ | ||
props: { | ||
id: 'number', | ||
label: 'string' | ||
} | ||
}); | ||
var attrs = {id: 1, label: 'c'}; | ||
var obj = new Model(attrs); | ||
obj.on('change', function () { changed += 1; }); | ||
obj.set(attrs); | ||
t.equal(changed, 0); | ||
t.end(); | ||
}); | ||
test("set triggers changes in the correct order", function (t) { | ||
var value = null; | ||
var M = State.extend({}); | ||
var model = new M(); | ||
model.on('last', function () { value = 'last'; }); | ||
model.on('first', function () { value = 'first'; }); | ||
model.trigger('first'); | ||
model.trigger('last'); | ||
t.equal(value, 'last'); | ||
t.end(); | ||
}); | ||
test("multiple unsets", function (t) { | ||
var i = 0; | ||
var counter = function () { i++; }; | ||
var Model = State.extend({ | ||
props: { | ||
a: 'string' | ||
} | ||
}); | ||
var model = new Model({a: 'a'}); | ||
model.on("change:a", counter); | ||
model.set({a: 'b'}); | ||
model.unset('a'); | ||
model.unset('a'); | ||
t.equal(i, 2, 'Unset does not fire an event for missing attributes.'); | ||
t.end(); | ||
}); | ||
test("unset and changedAttributes", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
a: 'number' | ||
} | ||
}); | ||
var model = new Model({a: 1}); | ||
model.on('change', function () { | ||
t.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties'); | ||
t.end(); | ||
}); | ||
model.unset('a'); | ||
}); | ||
test("change, hasChanged, changedAttributes, previous, previousAttributes", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
name: 'string', | ||
age: 'number' | ||
} | ||
}); | ||
var model = new Model({name: "Tim", age: 10}); | ||
t.deepEqual(model.changedAttributes(), false); | ||
model.on('change', function () { | ||
t.ok(model.hasChanged('name'), 'name changed'); | ||
t.ok(!model.hasChanged('age'), 'age did not'); | ||
t.deepEqual(model.changedAttributes(), {name : 'Rob'}, 'changedAttributes returns the changed attrs'); | ||
t.equal(model.previous('name'), 'Tim'); | ||
t.deepEqual(model.previousAttributes(), {name : "Tim", age : 10}, 'previousAttributes is correct'); | ||
t.end(); | ||
}); | ||
t.equal(model.hasChanged(), false); | ||
t.equal(model.hasChanged(undefined), false); | ||
model.set({name : 'Rob'}); | ||
t.equal(model.get('name'), 'Rob'); | ||
}); | ||
test("validate on unset and clear", function (t) { | ||
var error; | ||
var Model = State.extend({ | ||
props: { | ||
name: 'string' | ||
} | ||
}); | ||
var model = new Model({name: "One"}); | ||
model.validate = function (attrs) { | ||
if (!attrs.name) { | ||
error = true; | ||
return "No thanks."; | ||
} | ||
}; | ||
model.set({name: "Two"}); | ||
t.equal(model.get('name'), 'Two'); | ||
t.equal(error, undefined); | ||
model.unset('name', {validate: true}); | ||
t.equal(error, true); | ||
t.equal(model.get('name'), 'Two'); | ||
model.clear({validate: true}); | ||
t.equal(model.get('name'), 'Two'); | ||
delete model.validate; | ||
model.clear(); | ||
t.equal(model.get('name'), undefined); | ||
t.end(); | ||
}); | ||
test("validate with error callback", function (t) { | ||
var lastError, boundError; | ||
var Model = State.extend({ | ||
props: { | ||
a: 'number', | ||
admin: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.validate = function (attrs) { | ||
if (attrs.admin) return "Can't change admin status."; | ||
}; | ||
model.on('invalid', function (model, error) { | ||
boundError = true; | ||
}); | ||
var result = model.set({a: 100}, {validate: true}); | ||
t.equal(result, model); | ||
t.equal(model.get('a'), 100); | ||
t.equal(model.validationError, null); | ||
t.equal(boundError, undefined); | ||
result = model.set({a: 200, admin: true}, {validate: true}); | ||
t.equal(result, false); | ||
t.equal(model.get('a'), 100); | ||
t.equal(model.validationError, "Can't change admin status."); | ||
t.equal(boundError, true); | ||
t.end(); | ||
}); | ||
test("Nested change events don't clobber previous attributes", function (t) { | ||
new (State.extend({props: {state: 'string', other: 'string'}}))() | ||
.on('change:state', function (model, newState) { | ||
t.equal(model.previous('state'), undefined); | ||
t.equal(newState, 'hello'); | ||
// Fire a nested change event. | ||
model.set({other: 'whatever'}); | ||
}) | ||
.on('change:state', function (model, newState) { | ||
t.equal(model.previous('state'), undefined); | ||
t.equal(newState, 'hello'); | ||
t.end(); | ||
}) | ||
.set({state: 'hello'}); | ||
}); | ||
test("hasChanged/set should use same comparison", function (t) { | ||
var changed = 0; | ||
var Model = State.extend({ | ||
props: { | ||
a: 'string' | ||
} | ||
}); | ||
var model = new Model({a: 'something'}); | ||
model.on('change', function () { | ||
t.ok(this.hasChanged('a')); | ||
}) | ||
.on('change:a', function () { | ||
changed++; | ||
}) | ||
.set({a: 'else'}); | ||
t.equal(changed, 1); | ||
t.end(); | ||
}); | ||
test("#582, #425, change:attribute callbacks should fire after all changes have occurred", 9, function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
a: 'string', | ||
b: 'string', | ||
c: 'string' | ||
} | ||
}); | ||
var model = new Model(); | ||
var assertion = function () { | ||
t.equal(model.get('a'), 'a'); | ||
t.equal(model.get('b'), 'b'); | ||
t.equal(model.get('c'), 'c'); | ||
}; | ||
model.on('change:a', assertion); | ||
model.on('change:b', assertion); | ||
model.on('change:c', assertion); | ||
model.set({a: 'a', b: 'b', c: 'c'}); | ||
t.end(); | ||
}); | ||
test("set same value does not trigger change", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model({x: 1}); | ||
model.on('change change:x', function () { t.ok(false); }); | ||
model.set({x: 1}); | ||
model.set({x: 1}); | ||
t.end(); | ||
}); | ||
test("unset does not fire a change for undefined attributes", 0, function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model({x: undefined}); | ||
model.on('change:x', function () { t.ok(false); }); | ||
model.unset('x'); | ||
t.end(); | ||
}); | ||
test("hasChanged works outside of change events, and true within", 6, function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model({x: 1}); | ||
model.on('change:x', function () { | ||
t.ok(model.hasChanged('x')); | ||
t.equal(model.get('x'), 1); | ||
}); | ||
model.set({x: 2}, {silent: true}); | ||
t.ok(model.hasChanged()); | ||
t.equal(model.hasChanged('x'), true); | ||
model.set({x: 1}); | ||
t.ok(model.hasChanged()); | ||
t.equal(model.hasChanged('x'), true); | ||
t.end(); | ||
}); | ||
test("hasChanged gets cleared on the following set", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.set({x: 1}); | ||
t.ok(model.hasChanged()); | ||
model.set({x: 1}); | ||
t.ok(!model.hasChanged()); | ||
model.set({x: 2}); | ||
t.ok(model.hasChanged()); | ||
model.set({}); | ||
t.ok(!model.hasChanged()); | ||
t.end(); | ||
}); | ||
test("`hasChanged` for falsey keys", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.set({x: true}, {silent: true}); | ||
t.ok(!model.hasChanged(0)); | ||
t.ok(!model.hasChanged('')); | ||
t.end(); | ||
}); | ||
test("`previous` for falsey keys", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
0: 'boolean', | ||
'': 'boolean' | ||
} | ||
}); | ||
var model = new Model({0: true, '': true}); | ||
model.set({0: false, '': false}, {silent: true}); | ||
t.equal(model.previous(0), true); | ||
t.equal(model.previous(''), true); | ||
t.end(); | ||
}); | ||
test("validate", function (t) { | ||
var lastError; | ||
var Model = State.extend({ | ||
props: { | ||
admin: ['boolean', true, true], | ||
a: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.validate = function (attrs) { | ||
if (attrs.admin != this.get('admin')) return "Can't change admin status."; | ||
}; | ||
model.on('invalid', function (model, error) { | ||
lastError = error; | ||
}); | ||
var result = model.set({a: 100}); | ||
t.equal(result, model); | ||
t.equal(model.get('a'), 100); | ||
t.equal(lastError, undefined); | ||
result = model.set({admin: true}); | ||
t.equal(model.get('admin'), true); | ||
result = model.set({a: 200, admin: false}, {validate: true}); | ||
t.equal(lastError, "Can't change admin status."); | ||
t.equal(result, false); | ||
t.equal(model.get('a'), 100); | ||
t.end(); | ||
}); | ||
test("set and unset", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
id: 'string', | ||
foo: 'number', | ||
bar: 'number', | ||
baz: 'number', | ||
extra: 'string' | ||
} | ||
}); | ||
var a = new Model({id: 'id', foo: 1, bar: 2, baz: 3}); | ||
var changeCount = 0; | ||
a.on("change:foo", function () { changeCount += 1; }); | ||
a.set({'foo': 2}); | ||
t.ok(a.get('foo') == 2, "Foo should have changed."); | ||
t.ok(changeCount == 1, "Change count should have incremented."); | ||
a.set({'foo': 2}); // set with value that is not new shouldn't fire change event | ||
t.ok(a.get('foo') == 2, "Foo should NOT have changed, still 2"); | ||
t.ok(changeCount == 1, "Change count should NOT have incremented."); | ||
a.validate = function (attrs) { | ||
t.equal(attrs.foo, void 0, "validate: true passed while unsetting"); | ||
}; | ||
a.unset('foo', {validate: true}); | ||
t.equal(a.get('foo'), void 0, "Foo should have changed"); | ||
delete a.validate; | ||
t.ok(changeCount == 2, "Change count should have incremented for unset."); | ||
a.unset('id'); | ||
t.equal(a.id, undefined, "Unsetting the id should remove the id property."); | ||
t.end(); | ||
}); | ||
test("nested `set` during `'change:attr'`", function (t) { | ||
var events = []; | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean', | ||
y: 'boolean', | ||
z: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('all', function (event) { events.push(event); }); | ||
model.on('change', function () { | ||
model.set({z: true}, {silent: true}); | ||
}); | ||
model.on('change:x', function () { | ||
model.set({y: true}); | ||
}); | ||
model.set({x: true}); | ||
t.deepEqual(events, ['change:y', 'change:x', 'change']); | ||
events = []; | ||
model.set({z: true}); | ||
t.deepEqual(events, []); | ||
t.end(); | ||
}); | ||
test("nested `change` only fires once", function (t) { | ||
t.plan(1); | ||
var model = new (State.extend({props: {x: 'boolean'}}))(); | ||
model.on('change', function () { | ||
t.ok(true); | ||
model.set({x: true}); | ||
}); | ||
model.set({x: true}); | ||
}); | ||
test("nested `set` during `'change'`", function (t) { | ||
var count = 0; | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean', | ||
y: 'boolean', | ||
z: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change', function () { | ||
switch (count++) { | ||
case 0: | ||
t.deepEqual(this.changedAttributes(), {x: true}); | ||
t.equal(model.previous('x'), undefined); | ||
model.set({y: true}); | ||
break; | ||
case 1: | ||
t.deepEqual(this.changedAttributes(), {x: true, y: true}); | ||
t.equal(model.previous('x'), undefined); | ||
model.set({z: true}); | ||
break; | ||
case 2: | ||
t.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); | ||
t.equal(model.previous('y'), undefined); | ||
break; | ||
default: | ||
t.ok(false); | ||
} | ||
}); | ||
model.set({x: true}); | ||
t.end(); | ||
}); | ||
test("nested `change` with silent", function (t) { | ||
var count = 0; | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean', | ||
y: 'boolean', | ||
z: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change:y', function () { t.ok(false); }); | ||
model.on('change', function () { | ||
switch (count++) { | ||
case 0: | ||
t.deepEqual(this.changedAttributes(), {x: true}); | ||
model.set({y: true}, {silent: true}); | ||
model.set({z: true}); | ||
break; | ||
case 1: | ||
t.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); | ||
break; | ||
case 2: | ||
t.deepEqual(this.changedAttributes(), {z: false}); | ||
break; | ||
default: | ||
t.ok(false); | ||
} | ||
}); | ||
model.set({x: true}); | ||
model.set({z: false}); | ||
t.end(); | ||
}); | ||
test("nested `change:attr` with silent", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean', | ||
y: 'boolean', | ||
z: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change:y', function () { t.ok(false); }); | ||
model.on('change', function () { | ||
model.set({y: true}, {silent: true}); | ||
model.set({z: true}); | ||
}); | ||
model.set({x: true}); | ||
t.end(); | ||
}); | ||
test("multiple nested changes with silent", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'boolean', | ||
y: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change:x', function () { | ||
model.set({y: 1}, {silent: true}); | ||
model.set({y: 2}); | ||
}); | ||
model.on('change:y', function (model, val) { | ||
t.equal(val, 2); | ||
}); | ||
model.set({x: true}); | ||
t.end(); | ||
}); | ||
test("multiple nested changes with silent", function (t) { | ||
var changes = []; | ||
var Model = State.extend({ | ||
props: { | ||
b: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change:b', function (model, val) { changes.push(val); }); | ||
model.on('change', function () { | ||
model.set({b: 1}); | ||
}); | ||
model.set({b: 0}); | ||
t.deepEqual(changes, [0, 1]); | ||
t.end(); | ||
}); | ||
test("basic silent change semantics", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.set({x: 1}); | ||
model.on('change', function () { t.ok(true); }); | ||
model.set({x: 2}, {silent: true}); | ||
model.set({x: 1}); | ||
t.end(); | ||
}); | ||
test("nested set multiple times", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
a: 'boolean', | ||
b: 'boolean' | ||
} | ||
}); | ||
var model = new Model(); | ||
model.on('change:b', function () { | ||
t.ok(true); | ||
}); | ||
model.on('change:a', function () { | ||
model.set({b: true}); | ||
model.set({b: true}); | ||
}); | ||
model.set({a: true}); | ||
t.end(); | ||
}); | ||
test("#1122 - clear does not alter options.", function (t) { | ||
var model = new (State.extend({}))(); | ||
var options = {}; | ||
model.clear(options); | ||
t.ok(!options.unset); | ||
t.end(); | ||
}); | ||
test("#1122 - unset does not alter options.", function (t) { | ||
var Model = State.extend({ | ||
props: { | ||
x: 'number' | ||
} | ||
}); | ||
var model = new Model(); | ||
var options = {}; | ||
model.unset('x', options); | ||
t.ok(!options.unset); | ||
t.end(); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
99683
2558