Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
621
gaia/apps/settings/test/unit/modules/mvvm/list_view_test.js
Executable file
621
gaia/apps/settings/test/unit/modules/mvvm/list_view_test.js
Executable file
|
|
@ -0,0 +1,621 @@
|
|||
/* global define */
|
||||
'use strict';
|
||||
|
||||
// The views are very tightly coupled to models, rather than reimplement model
|
||||
// behavior in a mock, rely on tested interfaces on the model instead
|
||||
suite('ListView', function() {
|
||||
// Define a shim for ObservableArray that is to be loaded in ListView.
|
||||
// Then we are able to creat a stub on it.
|
||||
|
||||
suiteSetup(function(done) {
|
||||
// Use the shim in ListView.
|
||||
var ObservableArrayShimName = 'observable_array_shim' + Date.now();
|
||||
var map = {
|
||||
'modules/mvvm/list_view': {
|
||||
'modules/mvvm/observable_array': ObservableArrayShimName
|
||||
}
|
||||
};
|
||||
|
||||
// XXX: Create a new require context.
|
||||
// We can only define the mock module after retrieving the context.
|
||||
var requireCtx = testRequire([], map, function() {});
|
||||
// Define the mock module.
|
||||
define(ObservableArrayShimName, function() {
|
||||
var ctor = function observable_array_shim() {
|
||||
return ctor.mInnerFunction.apply(this, arguments);
|
||||
};
|
||||
ctor.mInnerFunction = function() {};
|
||||
ctor.mTeardown = function observable_array_shim_teardown() {
|
||||
ctor.mInnerFunction = function() {};
|
||||
};
|
||||
return ctor;
|
||||
});
|
||||
|
||||
requireCtx([
|
||||
'modules/mvvm/observable',
|
||||
'modules/mvvm/observable_array',
|
||||
'modules/mvvm/list_view',
|
||||
ObservableArrayShimName
|
||||
], (function(Observable, ObservableArray, ListView, ObservableArrayShim) {
|
||||
this.Observable = Observable;
|
||||
this.ListView = ListView;
|
||||
this.ObservableArrayShim = ObservableArrayShim;
|
||||
// stub the real ObservableArray to create spies on observe method
|
||||
// Redirect the shim to the ObservableArray stub.
|
||||
this.ObservableArray = sinon.stub(ObservableArrayShim,
|
||||
'mInnerFunction', function() {
|
||||
var result = ObservableArray.apply(this, arguments);
|
||||
sinon.spy(result, 'addEventListener');
|
||||
sinon.spy(result, 'removeEventListener');
|
||||
return result;
|
||||
});
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
this.ObservableArray.reset();
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
this.ObservableArray.restore();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.array = [];
|
||||
this.observableArray = this.ObservableArray(this.array);
|
||||
});
|
||||
|
||||
suite('ListView(<ul>, ObservableArray, function)', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('ul');
|
||||
this.template = (function(item, recycle) {
|
||||
if (recycle && this.template.recycle) {
|
||||
return recycle;
|
||||
}
|
||||
return document.createElement('li');
|
||||
}).bind(this);
|
||||
this.sinon.spy(this, 'template');
|
||||
this.listView = this.ListView(
|
||||
this.container, this.observableArray, this.template
|
||||
);
|
||||
});
|
||||
|
||||
test('.element', function() {
|
||||
assert.equal(this.listView.element, this.container);
|
||||
});
|
||||
|
||||
test('Listens to ObservableArray', function() {
|
||||
var addEventListener = this.observableArray.addEventListener;
|
||||
assert.ok(addEventListener.calledWith('insert'));
|
||||
assert.ok(addEventListener.calledWith('remove'));
|
||||
assert.ok(addEventListener.calledWith('replace'));
|
||||
assert.ok(addEventListener.calledWith('reset'));
|
||||
});
|
||||
|
||||
suite('Add Multiple Elements', function() {
|
||||
setup(function() {
|
||||
// add 3 items
|
||||
for (var count = 0; count < 3; count++) {
|
||||
this.observableArray.push({ count: count });
|
||||
}
|
||||
});
|
||||
|
||||
// use forEach instead of for to keep 'count' in a closure
|
||||
[0, 1, 2].forEach(function(count) {
|
||||
suite('item ' + count, function() {
|
||||
setup(function() {
|
||||
this.item = this.array[count];
|
||||
});
|
||||
test('kept order', function() {
|
||||
assert.equal(this.item.count, count);
|
||||
});
|
||||
test('called template', function() {
|
||||
assert.ok(this.template.calledWith(this.item));
|
||||
});
|
||||
test('template returned element is in correct position', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[count], li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Replace Element', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.item = { replacement: true };
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.observableArray.set(1, this.item);
|
||||
});
|
||||
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElements[1])
|
||||
);
|
||||
});
|
||||
|
||||
test('only called template method once', function() {
|
||||
assert.equal(this.template.callCount, 1);
|
||||
});
|
||||
|
||||
test('replaced element', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[1], li);
|
||||
});
|
||||
|
||||
test('leaves other elements alone', function() {
|
||||
assert.equal(this.container.children[0], this.originalElements[0]);
|
||||
assert.equal(this.container.children[2], this.originalElements[2]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Replace Element (w/recycle)', function() {
|
||||
setup(function() {
|
||||
this.template.recycle = true;
|
||||
this.template.reset();
|
||||
this.item = { replacement: true };
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.observableArray.set(1, this.item);
|
||||
});
|
||||
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElements[1])
|
||||
);
|
||||
});
|
||||
|
||||
test('only called template method once', function() {
|
||||
assert.equal(this.template.callCount, 1);
|
||||
});
|
||||
|
||||
test('same element still in place', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(li, this.originalElements[1], 'returned original');
|
||||
assert.equal(this.container.children[1], this.originalElements[1]);
|
||||
});
|
||||
|
||||
test('leaves other elements alone', function() {
|
||||
assert.equal(this.container.children[0], this.originalElements[0]);
|
||||
assert.equal(this.container.children[2], this.originalElements[2]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Reset With Shorter Array', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.originalArray = this.array;
|
||||
this.array = [{ reset: 1 }, { reset: 2 }];
|
||||
this.observableArray.reset(this.array);
|
||||
});
|
||||
|
||||
// use forEach instead of for to store 'index' in closure
|
||||
[0, 1].forEach(function(index) {
|
||||
suite('item ' + index, function() {
|
||||
setup(function() {
|
||||
this.item = this.array[index];
|
||||
this.originalElement = this.originalElements[index];
|
||||
});
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElement)
|
||||
);
|
||||
});
|
||||
test('replaced element', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[index], li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('removed extra elements', function() {
|
||||
assert.equal(this.container.children.length, 2);
|
||||
assert.equal(this.originalElements[2].parentNode, null);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Reset With Longer Array', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.originalArray = this.array;
|
||||
this.array = [{ reset: 1 }, { reset: 2 }, { reset: 3 }, { reset: 4 }];
|
||||
this.observableArray.reset(this.array);
|
||||
});
|
||||
|
||||
// use forEach instead of for to store 'index' in closure
|
||||
[0, 1, 2].forEach(function(index) {
|
||||
suite('item ' + index, function() {
|
||||
setup(function() {
|
||||
this.item = this.array[index];
|
||||
this.originalElement = this.originalElements[index];
|
||||
});
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElement)
|
||||
);
|
||||
});
|
||||
test('replaced element', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[index], li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('item 3', function() {
|
||||
setup(function() {
|
||||
this.item = this.array[3];
|
||||
});
|
||||
|
||||
test('called template without recycled element', function() {
|
||||
assert.ok(this.template.calledWith(this.item));
|
||||
assert.equal(
|
||||
this.template.withArgs(this.item).args[0][1], undefined
|
||||
);
|
||||
});
|
||||
|
||||
test('added extra element', function() {
|
||||
assert.equal(this.container.children.length, 4);
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[3], li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('reset with .set([{},{}])', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.originalArray = this.array;
|
||||
this.array = [{ reset: 1 }, { reset: 2 }];
|
||||
this.listView.set(this.array);
|
||||
});
|
||||
|
||||
test('creates and watches an ObservableArray', function() {
|
||||
var withNew = this.ObservableArray.withArgs(this.array);
|
||||
assert.ok(withNew.called);
|
||||
var addEventListener = withNew.returnValues[0].addEventListener;
|
||||
assert.ok(addEventListener.calledWith('insert'));
|
||||
assert.ok(addEventListener.calledWith('remove'));
|
||||
assert.ok(addEventListener.calledWith('replace'));
|
||||
assert.ok(addEventListener.calledWith('reset'));
|
||||
});
|
||||
|
||||
// use forEach instead of for to store 'index' in closure
|
||||
[0, 1].forEach(function(index) {
|
||||
suite('item ' + index, function() {
|
||||
setup(function() {
|
||||
this.item = this.array[index];
|
||||
this.originalElement = this.originalElements[index];
|
||||
});
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElement)
|
||||
);
|
||||
});
|
||||
test('replaced element', function() {
|
||||
var li = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[index], li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('removed extra elements', function() {
|
||||
assert.equal(this.container.children.length, 2);
|
||||
assert.equal(this.originalElements[2].parentNode, null);
|
||||
});
|
||||
});
|
||||
|
||||
suite('.enabled = false', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.originalElements =
|
||||
Array.prototype.slice.call(this.container.children);
|
||||
this.listView.enabled = false;
|
||||
});
|
||||
// test a bunch of things that modify the array
|
||||
[function pop() {
|
||||
this.observableArray.pop();
|
||||
}, function push() {
|
||||
this.observableArray.push({});
|
||||
}, function replace() {
|
||||
this.observableArray.set(1, {});
|
||||
}, function reset() {
|
||||
this.array = [{}];
|
||||
this.observableArray.reset(this.array);
|
||||
}, function set() {
|
||||
this.array = [{}];
|
||||
this.listView.set(this.array);
|
||||
}].forEach(function(method) {
|
||||
suite(method.name, function() {
|
||||
setup(method);
|
||||
test('does not call template', function() {
|
||||
assert.equal(this.template.called, 0);
|
||||
});
|
||||
test('does not change elements', function() {
|
||||
for (var i = 0; i < this.container.children.length; i++) {
|
||||
assert.equal(this.container.children[i],
|
||||
this.originalElements[i]);
|
||||
}
|
||||
});
|
||||
suite('.enabled = true', function() {
|
||||
setup(function() {
|
||||
this.listView.enabled = true;
|
||||
});
|
||||
test('calls template for each item in array', function() {
|
||||
this.array.forEach(function(item, index) {
|
||||
assert.ok(this.template.calledWith(
|
||||
item, this.originalElements[index]
|
||||
), 'array[' + index + ']');
|
||||
}, this);
|
||||
});
|
||||
test('elements are put in the right place', function() {
|
||||
this.array.forEach(function(item, index) {
|
||||
var template = this.template.withArgs(item);
|
||||
assert.equal(
|
||||
this.container.children[index], template.returnValues[0],
|
||||
'element[' + index + ']'
|
||||
);
|
||||
}, this);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('ListView(<div>, ObservableArray, function)', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
// ensure content inside the container at creation doesn't interfere
|
||||
this.container.appendChild(document.createElement('span'));
|
||||
this.template = (function(item, recycle) {
|
||||
if (recycle && this.template.recycle) {
|
||||
return recycle;
|
||||
}
|
||||
return document.createElement('div');
|
||||
}).bind(this);
|
||||
this.sinon.spy(this, 'template');
|
||||
this.observableArray.push({ count: 0 });
|
||||
this.listView = this.ListView(
|
||||
this.container, this.observableArray, this.template
|
||||
);
|
||||
});
|
||||
|
||||
test('.element', function() {
|
||||
assert.equal(this.listView.element, this.container);
|
||||
});
|
||||
|
||||
test('Listens to ObservableArray', function() {
|
||||
var addEventListener = this.observableArray.addEventListener;
|
||||
assert.ok(addEventListener.calledWith('insert'));
|
||||
assert.ok(addEventListener.calledWith('remove'));
|
||||
assert.ok(addEventListener.calledWith('replace'));
|
||||
assert.ok(addEventListener.calledWith('reset'));
|
||||
});
|
||||
|
||||
test('Initial Element doesnt recycle', function() {
|
||||
var item = this.array[0];
|
||||
var filtered = this.template.withArgs(item);
|
||||
// ensures that if there happens to be an element in the creation
|
||||
// of the ListView, it is not used as a "recycled" element
|
||||
assert.ok(filtered.callCount, 1);
|
||||
assert.equal(filtered.args[0][1], undefined);
|
||||
});
|
||||
|
||||
suite('Add Multiple Elements', function() {
|
||||
setup(function() {
|
||||
// add 2 more items
|
||||
for (var count = 1; count < 3; count++) {
|
||||
this.observableArray.push({ count: count });
|
||||
}
|
||||
});
|
||||
|
||||
// use forEach instead of for to keep 'count' in a closure
|
||||
[1, 2].forEach(function(count) {
|
||||
suite('item ' + count, function() {
|
||||
setup(function() {
|
||||
this.item = this.array[count];
|
||||
});
|
||||
test('kept order', function() {
|
||||
assert.equal(this.item.count, count);
|
||||
});
|
||||
test('called template', function() {
|
||||
assert.ok(this.template.calledWith(this.item));
|
||||
});
|
||||
test('template returned element is in correct position', function() {
|
||||
var div = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[count], div);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Replace Element', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.item = { replacement: true };
|
||||
this.originalElements = [].slice.call(this.container.children);
|
||||
this.observableArray.set(1, this.item);
|
||||
});
|
||||
|
||||
test('called template with recycled element', function() {
|
||||
assert.ok(
|
||||
this.template.calledWith(this.item, this.originalElements[1])
|
||||
);
|
||||
});
|
||||
|
||||
test('only called template method once', function() {
|
||||
assert.equal(this.template.callCount, 1);
|
||||
});
|
||||
|
||||
test('replaced element', function() {
|
||||
var div = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[1], div);
|
||||
});
|
||||
|
||||
test('leaves other elements alone', function() {
|
||||
assert.equal(this.container.children[0], this.originalElements[0]);
|
||||
assert.equal(this.container.children[2], this.originalElements[2]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Remove Element', function() {
|
||||
setup(function() {
|
||||
this.template.reset();
|
||||
this.originalElements = [].slice.call(this.container.children);
|
||||
this.observableArray.pop();
|
||||
});
|
||||
|
||||
test('removed last element', function() {
|
||||
assert.equal(this.originalElements[2].parentNode, null);
|
||||
});
|
||||
|
||||
test('leaves other elements alone', function() {
|
||||
assert.equal(this.container.children[0], this.originalElements[0]);
|
||||
assert.equal(this.container.children[1], this.originalElements[1]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Insert Element', function() {
|
||||
setup(function() {
|
||||
this.originalElements = [].slice.call(this.container.children);
|
||||
this.item = {};
|
||||
this.template.reset();
|
||||
this.observableArray.splice(0, 0, this.item);
|
||||
});
|
||||
|
||||
test('only called template method once', function() {
|
||||
assert.equal(this.template.callCount, 1);
|
||||
});
|
||||
|
||||
test('inserted element', function() {
|
||||
var div = this.template.withArgs(this.item).returnValues[0];
|
||||
assert.equal(this.container.children[0], div);
|
||||
});
|
||||
|
||||
test('shifts other elements', function() {
|
||||
assert.equal(this.container.children[1], this.originalElements[0]);
|
||||
assert.equal(this.container.children[2], this.originalElements[1]);
|
||||
assert.equal(this.container.children[3], this.originalElements[2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('observable hooks', function() {
|
||||
setup(function() {
|
||||
this.observable = this.Observable({ test: 1 });
|
||||
this.observableArray = this.ObservableArray([this.observable]);
|
||||
this.hook = this.sinon.spy();
|
||||
this.template = (function(data, recycle, helper) {
|
||||
helper.observeAndCall(data, {
|
||||
test: this.hook
|
||||
});
|
||||
return recycle || document.createElement('li');
|
||||
}).bind(this);
|
||||
this.container = document.createElement('ul');
|
||||
this.sinon.spy(this, 'template');
|
||||
this.listView = this.ListView(
|
||||
this.container, this.observableArray, this.template
|
||||
);
|
||||
});
|
||||
|
||||
test('hook called at creation', function() {
|
||||
assert.equal(this.hook.callCount, 1);
|
||||
});
|
||||
|
||||
test('hook called when observable changes', function() {
|
||||
this.hook.reset();
|
||||
this.observable.test = false;
|
||||
assert.equal(this.hook.callCount, 1);
|
||||
});
|
||||
|
||||
test('unobserves when removing', function() {
|
||||
this.sinon.spy(this.observable, 'unobserve');
|
||||
this.observableArray.splice(0, 1);
|
||||
assert.ok(this.observable.unobserve.calledWith(this.hook));
|
||||
});
|
||||
|
||||
test('unobserves when replacing', function() {
|
||||
this.sinon.spy(this.observable, 'unobserve');
|
||||
this.sinon.spy(this.observable, 'observe');
|
||||
this.observableArray.set(0, this.observable);
|
||||
// should unobserve
|
||||
assert.ok(this.observable.unobserve.calledWith(this.hook));
|
||||
// and also observe
|
||||
assert.ok(this.observable.observe.calledWith('test', this.hook));
|
||||
// but should unobserve first
|
||||
assert.ok(
|
||||
this.observable.unobserve.calledBefore(this.observable.observe)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('destroy', function() {
|
||||
setup(function() {
|
||||
this.observableArray = this.ObservableArray([1, 2, 3]);
|
||||
this.container = document.createElement('div');
|
||||
this.listView = this.ListView(
|
||||
this.container, this.observableArray, function() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('removes children', function() {
|
||||
this.listView.destroy();
|
||||
assert.equal(this.container.children.length, 0);
|
||||
});
|
||||
|
||||
test('sanity check', function() {
|
||||
assert.equal(this.observableArray.removeEventListener.callCount, 0);
|
||||
assert.equal(this.container.children.length, 3);
|
||||
});
|
||||
|
||||
test('.element', function() {
|
||||
this.listView.destroy();
|
||||
assert.equal(this.listView.element, null);
|
||||
});
|
||||
|
||||
test('unbinds from observeable', function() {
|
||||
this.listView.destroy();
|
||||
// called call removeEventListener with a method
|
||||
['insert', 'remove', 'replace', 'reset'].forEach((eventName, index) => {
|
||||
assert.isTrue(
|
||||
this.observableArray.removeEventListener.calledWith(eventName));
|
||||
assert.isFunction(
|
||||
this.observableArray.removeEventListener.args[index][1]);
|
||||
});
|
||||
});
|
||||
|
||||
test('creating another ListView destroys old', function() {
|
||||
this.observableArray.reset();
|
||||
this.sinon.spy(this.listView, 'destroy');
|
||||
// create a new one, we don't care about what happens to it.
|
||||
this.ListView(
|
||||
this.container, this.observableArray, function() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
);
|
||||
|
||||
// destroys old view
|
||||
assert.ok(this.listView.destroy.called);
|
||||
// unbinds from observable
|
||||
assert.ok(this.observableArray.removeEventListener.called);
|
||||
// re-observes
|
||||
assert.ok(this.observableArray.removeEventListener.called);
|
||||
// and make sure its done in the right order
|
||||
assert.ok(
|
||||
this.observableArray.removeEventListener.calledBefore(
|
||||
this.observableArray.removeEventListener
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
231
gaia/apps/settings/test/unit/modules/mvvm/observable_array_test.js
Executable file
231
gaia/apps/settings/test/unit/modules/mvvm/observable_array_test.js
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
'use strict';
|
||||
|
||||
suite('ObservableArray', function() {
|
||||
var methods = [
|
||||
'forEach', 'push', 'pop', 'splice', 'set', 'get', 'reset', 'observe'
|
||||
];
|
||||
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/mvvm/observable_array'], (function(ObservableArray) {
|
||||
this.ObservableArray = ObservableArray;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.array = methods.slice();
|
||||
this.observable = this.ObservableArray(this.array);
|
||||
});
|
||||
|
||||
suite('shape: ObservableArray(array)', function() {
|
||||
methods.forEach(function(method) {
|
||||
test('.' + method + ' is a function', function() {
|
||||
assert.equal(typeof this.observable[method], 'function');
|
||||
});
|
||||
});
|
||||
test('.length is a number', function() {
|
||||
assert.equal(typeof this.observable.length, 'number');
|
||||
});
|
||||
test('.array is the original array', function() {
|
||||
assert.equal(this.observable.array, this.array);
|
||||
});
|
||||
});
|
||||
|
||||
test('get()', function() {
|
||||
assert.ok(this.array.length);
|
||||
for (var index = 0; index < this.array.length; index++) {
|
||||
assert.equal(this.observable.get(index), this.array[index],
|
||||
'index: ' + index);
|
||||
}
|
||||
});
|
||||
|
||||
suite('observable actions', function() {
|
||||
var events = ['insert', 'remove', 'replace', 'reset'];
|
||||
|
||||
function checkSpies(expected) {
|
||||
events.forEach(function(event) {
|
||||
var expectedCount = expected[event] || 0;
|
||||
test(event + ' called ' + expectedCount + ' times', function() {
|
||||
assert.equal(this.spies[event].args.length, expectedCount);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkArgs(event) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
test(event + ' called with correct arguments', function() {
|
||||
var calls = this.spies[event].args;
|
||||
assert.deepEqual(calls[calls.length - 1], args);
|
||||
});
|
||||
}
|
||||
|
||||
function resetSpies(environment) {
|
||||
events.forEach(function(event) {
|
||||
this.spies[event].reset();
|
||||
}, environment);
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
this.spies = {};
|
||||
events.forEach(function(event) {
|
||||
this.spies[event] = this.sinon.spy();
|
||||
this.observable.addEventListener(event, this.spies[event]);
|
||||
}, this);
|
||||
});
|
||||
|
||||
suite('pop()', function() {
|
||||
setup(function() {
|
||||
this.observable.pop();
|
||||
});
|
||||
checkSpies({ remove: 1 });
|
||||
checkArgs('remove', {
|
||||
type: 'remove',
|
||||
detail: {
|
||||
index: methods.length - 1,
|
||||
count: 1,
|
||||
items: ['observe']
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expected = methods.slice();
|
||||
expected.pop();
|
||||
assert.deepEqual(this.array, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('push()', function() {
|
||||
setup(function() {
|
||||
this.observable.push('test');
|
||||
});
|
||||
checkSpies({ insert: 1 });
|
||||
checkArgs('insert', {
|
||||
type: 'insert',
|
||||
detail: {
|
||||
index: methods.length,
|
||||
count: 1,
|
||||
items: ['test']
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expected = methods.slice();
|
||||
expected.push('test');
|
||||
assert.deepEqual(this.array, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('splice(1, 2)', function() {
|
||||
setup(function() {
|
||||
this.observable.splice(1, 2);
|
||||
});
|
||||
checkSpies({ remove: 1 });
|
||||
checkArgs('remove', {
|
||||
type: 'remove',
|
||||
detail: {
|
||||
index: 1,
|
||||
count: 2,
|
||||
items: ['push', 'pop']
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expect = methods.slice();
|
||||
expect.splice(1, 2);
|
||||
assert.deepEqual(this.array, expect);
|
||||
});
|
||||
});
|
||||
|
||||
suite('splice(1, 2, 3, 4)', function() {
|
||||
setup(function() {
|
||||
this.observable.splice(1, 2, 3, 4);
|
||||
});
|
||||
checkSpies({ remove: 1, insert: 1 });
|
||||
checkArgs('remove', {
|
||||
type: 'remove',
|
||||
detail: {
|
||||
index: 1,
|
||||
count: 2,
|
||||
items: ['push', 'pop']
|
||||
}
|
||||
});
|
||||
checkArgs('insert', {
|
||||
type: 'insert',
|
||||
detail: {
|
||||
index: 1,
|
||||
count: 2,
|
||||
items: [3, 4]
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expected = methods.slice();
|
||||
expected.splice(1, 2, 3, 4);
|
||||
assert.deepEqual(this.array, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('splice(2, 0, 2)', function() {
|
||||
setup(function() {
|
||||
this.observable.splice(2, 0, 2);
|
||||
});
|
||||
checkSpies({ insert: 1 });
|
||||
checkArgs('insert', {
|
||||
type: 'insert',
|
||||
detail: {
|
||||
index: 2,
|
||||
count: 1,
|
||||
items: [2]
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expected = methods.slice();
|
||||
expected.splice(2, 0, 2);
|
||||
assert.deepEqual(this.array, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('set(7, true)', function() {
|
||||
setup(function() {
|
||||
this.observable.set(7, true);
|
||||
});
|
||||
checkSpies({ replace: 1 });
|
||||
checkArgs('replace', {
|
||||
type: 'replace',
|
||||
detail: {
|
||||
index: 7,
|
||||
oldValue: methods[7],
|
||||
newValue: true
|
||||
}
|
||||
});
|
||||
test('modifies array', function() {
|
||||
var expected = methods.slice();
|
||||
expected[7] = true;
|
||||
assert.deepEqual(this.array, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('reset([])', function() {
|
||||
var testArray = [true, false];
|
||||
setup(function() {
|
||||
this.observable.reset(testArray);
|
||||
});
|
||||
checkSpies({ reset: 1 });
|
||||
checkArgs('reset', {
|
||||
type: 'reset',
|
||||
detail: {
|
||||
items: testArray
|
||||
}
|
||||
});
|
||||
test('changes array', function() {
|
||||
assert.equal(this.observable.array, testArray);
|
||||
});
|
||||
});
|
||||
|
||||
suite('pop() on empty', function() {
|
||||
setup(function() {
|
||||
this.observable.reset([]);
|
||||
resetSpies(this);
|
||||
this.observable.pop();
|
||||
});
|
||||
// no events expected
|
||||
checkSpies({});
|
||||
});
|
||||
});
|
||||
});
|
||||
358
gaia/apps/settings/test/unit/modules/mvvm/observable_test.js
Executable file
358
gaia/apps/settings/test/unit/modules/mvvm/observable_test.js
Executable file
|
|
@ -0,0 +1,358 @@
|
|||
'use strict';
|
||||
|
||||
suite('Observable', function() {
|
||||
var template = {
|
||||
testBoolean: true,
|
||||
testNumber: 10,
|
||||
testString: 'test',
|
||||
'test object': {}
|
||||
// currently disabled - we don't handle functions
|
||||
// testFunction: function() {}
|
||||
};
|
||||
|
||||
function executeTest() {
|
||||
test('shape: Observable({}).observe is a function', function() {
|
||||
var observable = this.Observable({});
|
||||
assert.equal(typeof observable.observe, 'function');
|
||||
});
|
||||
test('shape: Observable({}).unobserve is a function', function() {
|
||||
var observable = this.Observable({});
|
||||
assert.equal(typeof observable.unobserve, 'function');
|
||||
});
|
||||
|
||||
suite('Creates observable properties', function() {
|
||||
var props = Object.keys(template);
|
||||
setup(function() {
|
||||
this.observable = this.Observable(template);
|
||||
});
|
||||
|
||||
// some tests for each property
|
||||
props.forEach(function(prop) {
|
||||
test('prop: "' + prop + '" copied value', function() {
|
||||
assert.equal(this.observable[prop], template[prop]);
|
||||
});
|
||||
|
||||
// test some stuff for observing
|
||||
suite('prop: "' + prop + '" observe', function() {
|
||||
setup(function() {
|
||||
this.spy = this.sinon.spy();
|
||||
this.observable.observe(prop, this.spy);
|
||||
});
|
||||
test('does not generate any callbacks when attaching', function() {
|
||||
assert.isFalse(this.spy.called);
|
||||
});
|
||||
|
||||
test('does not notify when set to same value', function() {
|
||||
this.observable[prop] = template[prop];
|
||||
assert.isFalse(this.spy.called);
|
||||
});
|
||||
|
||||
test('notifies when set to new value', function() {
|
||||
this.newValue = {};
|
||||
this.observable[prop] = this.newValue;
|
||||
assert.isTrue(this.spy.calledWith(this.newValue, template[prop]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('unobserve', function() {
|
||||
setup(function() {
|
||||
// bind some listeners
|
||||
this.spies = [this.sinon.spy(), this.sinon.spy()];
|
||||
props.forEach(function(prop) {
|
||||
this.observable.observe(prop, this.spies[0]);
|
||||
this.observable.observe(prop, this.spies[1]);
|
||||
}, this);
|
||||
});
|
||||
test('(property, handler)', function() {
|
||||
// remove handler for property
|
||||
this.observable.unobserve('testNumber', this.spies[0]);
|
||||
// change property - only other handler should be called
|
||||
this.observable.testNumber = {};
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
// change a different property, both handlers should be called
|
||||
this.observable.testString = {};
|
||||
assert.equal(this.spies[0].callCount, 1);
|
||||
assert.equal(this.spies[1].callCount, 2);
|
||||
});
|
||||
test('(handler)', function() {
|
||||
// remove handler for all properties
|
||||
this.observable.unobserve(this.spies[0]);
|
||||
// change property - only other handler should be called
|
||||
this.observable.testNumber = {};
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
// change a different property, only other handler should be called
|
||||
this.observable.testString = {};
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 2);
|
||||
});
|
||||
test('(property)', function() {
|
||||
// remove handler for all properties
|
||||
this.observable.unobserve('testNumber');
|
||||
// change property - neither handler should be called
|
||||
this.observable.testNumber = {};
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 0);
|
||||
// change a different property, both handlers should be called
|
||||
this.observable.testString = {};
|
||||
assert.equal(this.spies[0].callCount, 1);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suite('Object form', function() {
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/mvvm/observable'], (function(Observable) {
|
||||
this.Observable = Observable;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
executeTest();
|
||||
});
|
||||
|
||||
suite('Prototype form', function() {
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/base/module', 'modules/mvvm/observable'],
|
||||
(function(Module, Observable) {
|
||||
var NewObservable = Module.create(function() {
|
||||
this.super(Observable).call(this);
|
||||
}).extend(Observable);
|
||||
Object.keys(template).forEach(function (key) {
|
||||
Observable.defineObservableProperty(NewObservable.prototype, key, {
|
||||
value: template[key]
|
||||
});
|
||||
});
|
||||
this.Observable = NewObservable;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
executeTest();
|
||||
});
|
||||
|
||||
suite('Read-only property', function() {
|
||||
var props = ['prop1', 'prop2'];
|
||||
var defaultPropValues = ['value1', 'value2'];
|
||||
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/base/module', 'modules/mvvm/observable'],
|
||||
(function(Module, Observable) {
|
||||
var NewObservable = Module.create(function() {
|
||||
this.super(Observable).call(this);
|
||||
}).extend(Observable);
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[0], {
|
||||
readonly: true,
|
||||
value: defaultPropValues[0]
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[1], {
|
||||
readonly: true,
|
||||
value: defaultPropValues[1]
|
||||
});
|
||||
this.Observable = NewObservable;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
test('unable to set the property', function() {
|
||||
var observable = this.Observable();
|
||||
assert.throw(function() {
|
||||
observable[props[0]] = 300;
|
||||
}, TypeError, 'setting a property that has only a getter');
|
||||
});
|
||||
|
||||
test('internal property exists', function() {
|
||||
var observable = this.Observable();
|
||||
assert.equal(observable['_' + props[0]], defaultPropValues[0]);
|
||||
assert.equal(observable['_' + props[1]], defaultPropValues[1]);
|
||||
});
|
||||
|
||||
test('calls to the listeners when the internal property changes',
|
||||
function(done) {
|
||||
var newPropValue = 'newFakeValue';
|
||||
var observable = this.Observable();
|
||||
observable.observe(props[1], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[1]);
|
||||
assert.equal(newValue, newPropValue);
|
||||
done();
|
||||
});
|
||||
observable['_' + props[1]] = newPropValue;
|
||||
});
|
||||
});
|
||||
|
||||
suite('Dependency property', function() {
|
||||
var props = ['prop1', 'prop2'];
|
||||
var defaultPropValues = [2, 3];
|
||||
var dependencyProps = ['dprop1', 'dprop2', 'dprop3'];
|
||||
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/base/module', 'modules/mvvm/observable'],
|
||||
(function(Module, Observable) {
|
||||
var NewObservable = Module.create(function() {
|
||||
this.super(Observable).call(this);
|
||||
}).extend(Observable);
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[0], {
|
||||
value: defaultPropValues[0]
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[1], {
|
||||
value: defaultPropValues[1]
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[0], {
|
||||
dependency: props,
|
||||
get: function() {
|
||||
return this[props[0]] + this[props[1]];
|
||||
}
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[1], {
|
||||
dependency: [dependencyProps[0]],
|
||||
get: function() {
|
||||
return this[dependencyProps[0]];
|
||||
}
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[2], {
|
||||
dependency: [dependencyProps[0]],
|
||||
get: function() {
|
||||
return this[dependencyProps[0]];
|
||||
}
|
||||
});
|
||||
this.Observable = NewObservable;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
test('The property responds to the dependent property changes correctly',
|
||||
function(done) {
|
||||
var newPropValue = 100;
|
||||
var observable = this.Observable();
|
||||
observable.observe(dependencyProps[0], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
done();
|
||||
});
|
||||
observable[props[1]] = newPropValue;
|
||||
});
|
||||
|
||||
test('The property responds to the dependency property changes correctly',
|
||||
function(done) {
|
||||
var newPropValue = 100;
|
||||
var observable = this.Observable();
|
||||
var changeCount = 0;
|
||||
|
||||
observable.observe(dependencyProps[1], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
changeCount++;
|
||||
if (changeCount == 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
observable.observe(dependencyProps[2], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
changeCount++;
|
||||
if (changeCount == 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
observable[props[1]] = newPropValue;
|
||||
});
|
||||
});
|
||||
|
||||
suite('Extendibility', function() {
|
||||
var props = ['prop1', 'prop2'];
|
||||
var defaultPropValues = [2, 3];
|
||||
var dependencyProps = ['dprop1', 'dprop2', 'dprop3'];
|
||||
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/base/module', 'modules/mvvm/observable'],
|
||||
(function(Module, Observable) {
|
||||
var NewObservable = Module.create(function() {
|
||||
this.super(Observable).call(this);
|
||||
}).extend(Observable);
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[0], {
|
||||
value: defaultPropValues[0]
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, props[1], {
|
||||
value: defaultPropValues[1]
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[0], {
|
||||
dependency: props,
|
||||
get: function() {
|
||||
return this[props[0]] + this[props[1]];
|
||||
}
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[1], {
|
||||
dependency: [dependencyProps[0]],
|
||||
get: function() {
|
||||
return this[dependencyProps[0]];
|
||||
}
|
||||
});
|
||||
Observable.defineObservableProperty(
|
||||
NewObservable.prototype, dependencyProps[2], {
|
||||
dependency: [dependencyProps[0]],
|
||||
get: function() {
|
||||
return this[dependencyProps[0]];
|
||||
}
|
||||
});
|
||||
|
||||
var NewObservable2 = Module.create(function() {
|
||||
this.super(NewObservable).call(this);
|
||||
}).extend(NewObservable);
|
||||
this.Observable = NewObservable2;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
test('The property responds to the dependent property changes correctly',
|
||||
function(done) {
|
||||
var newPropValue = 100;
|
||||
var observable = this.Observable();
|
||||
observable.observe(dependencyProps[0], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
done();
|
||||
});
|
||||
observable[props[1]] = newPropValue;
|
||||
});
|
||||
|
||||
test('The property responds to the dependency property changes correctly',
|
||||
function(done) {
|
||||
var newPropValue = 100;
|
||||
var observable = this.Observable();
|
||||
var changeCount = 0;
|
||||
|
||||
observable.observe(dependencyProps[1], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
changeCount++;
|
||||
if (changeCount == 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
observable.observe(dependencyProps[2], function(newValue, oldValue) {
|
||||
assert.equal(oldValue, defaultPropValues[0] + defaultPropValues[1]);
|
||||
assert.equal(newValue, defaultPropValues[0] + newPropValue);
|
||||
changeCount++;
|
||||
if (changeCount == 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
observable[props[1]] = newPropValue;
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue