Socket
Socket
Sign inDemoInstall

ampersand-state

Package Overview
Dependencies
Maintainers
2
Versions
65
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ampersand-state - npm Package Compare versions

Comparing version 4.3.0 to 4.3.1

2

package.json
{
"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 &amp; Bob');
doc.set({audience: 'Tim > Joan'});
t.equal(doc.escape('audience'), 'Tim &gt; 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();
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc