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
618
gaia/apps/bluetooth/test/unit/modules/mvvm/list_view_test.js
Executable file
618
gaia/apps/bluetooth/test/unit/modules/mvvm/list_view_test.js
Executable file
|
|
@ -0,0 +1,618 @@
|
|||
/* 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, 'observe');
|
||||
sinon.spy(result, 'unobserve');
|
||||
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 observe = this.observableArray.observe;
|
||||
assert.ok(observe.calledWith('insert'));
|
||||
assert.ok(observe.calledWith('remove'));
|
||||
assert.ok(observe.calledWith('replace'));
|
||||
assert.ok(observe.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 observe = withNew.returnValues[0].observe;
|
||||
assert.ok(observe.calledWith('insert'));
|
||||
assert.ok(observe.calledWith('remove'));
|
||||
assert.ok(observe.calledWith('replace'));
|
||||
assert.ok(observe.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 observe = this.observableArray.observe;
|
||||
assert.ok(observe.calledWith('insert'));
|
||||
assert.ok(observe.calledWith('remove'));
|
||||
assert.ok(observe.calledWith('replace'));
|
||||
assert.ok(observe.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.unobserve.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 unobserve with a method
|
||||
assert.equal(this.observableArray.unobserve.callCount, 1);
|
||||
var arg = this.observableArray.unobserve.args[0][0];
|
||||
assert.isFunction(arg);
|
||||
});
|
||||
|
||||
test('creating another ListView destroys old', function() {
|
||||
this.observableArray.observe.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.unobserve.called);
|
||||
// re-observes
|
||||
assert.ok(this.observableArray.observe.called);
|
||||
// and make sure its done in the right order
|
||||
assert.ok(
|
||||
this.observableArray.unobserve.calledBefore(
|
||||
this.observableArray.observe
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
275
gaia/apps/bluetooth/test/unit/modules/mvvm/observable_array_test.js
Executable file
275
gaia/apps/bluetooth/test/unit/modules/mvvm/observable_array_test.js
Executable file
|
|
@ -0,0 +1,275 @@
|
|||
'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.observe(event, this.spies[event]);
|
||||
}, this);
|
||||
});
|
||||
|
||||
suite('pop()', function() {
|
||||
setup(function() {
|
||||
this.observable.pop();
|
||||
});
|
||||
checkSpies({ remove: 1 });
|
||||
checkArgs('remove', {
|
||||
type: 'remove',
|
||||
data: {
|
||||
index: methods.length - 1,
|
||||
count: 1
|
||||
}
|
||||
});
|
||||
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',
|
||||
data: {
|
||||
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',
|
||||
data: {
|
||||
index: 1,
|
||||
count: 2
|
||||
}
|
||||
});
|
||||
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',
|
||||
data: {
|
||||
index: 1,
|
||||
count: 2
|
||||
}
|
||||
});
|
||||
checkArgs('insert', {
|
||||
type: 'insert',
|
||||
data: {
|
||||
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',
|
||||
data: {
|
||||
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',
|
||||
data: {
|
||||
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',
|
||||
data: {
|
||||
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({});
|
||||
});
|
||||
|
||||
suite('unobserve', function() {
|
||||
setup(function() {
|
||||
// bind some listeners
|
||||
this.spies = [this.sinon.spy(), this.sinon.spy()];
|
||||
events.forEach(function(event) {
|
||||
this.observable.observe(event, this.spies[0]);
|
||||
this.observable.observe(event, this.spies[1]);
|
||||
}, this);
|
||||
});
|
||||
test('(property, handler)', function() {
|
||||
// remove handler for property
|
||||
this.observable.unobserve('insert', this.spies[0]);
|
||||
// insert, only other handler should be called
|
||||
this.observable.push({});
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
// remove, both handlers should be called
|
||||
this.observable.pop();
|
||||
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]);
|
||||
// insert, only other handler should be called
|
||||
this.observable.push({});
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
// remove, only other handler should be called
|
||||
this.observable.pop();
|
||||
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('insert');
|
||||
// insert, neither handler should be called
|
||||
this.observable.push({});
|
||||
assert.equal(this.spies[0].callCount, 0);
|
||||
assert.equal(this.spies[1].callCount, 0);
|
||||
// remove, both handlers should be called
|
||||
this.observable.pop({});
|
||||
assert.equal(this.spies[0].callCount, 1);
|
||||
assert.equal(this.spies[1].callCount, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
110
gaia/apps/bluetooth/test/unit/modules/mvvm/observable_test.js
Executable file
110
gaia/apps/bluetooth/test/unit/modules/mvvm/observable_test.js
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
'use strict';
|
||||
|
||||
suite('Observable', function() {
|
||||
suiteSetup(function(done) {
|
||||
testRequire(['modules/mvvm/observable'], (function(Observable) {
|
||||
this.Observable = Observable;
|
||||
done();
|
||||
}).bind(this));
|
||||
});
|
||||
|
||||
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.observe, 'function');
|
||||
});
|
||||
|
||||
suite('Creates observable properties', function() {
|
||||
var template = {
|
||||
testBoolean: true,
|
||||
testNumber: 10,
|
||||
testString: 'test',
|
||||
'test object': {}
|
||||
// currently disabled - we don't handle functions
|
||||
// testFunction: 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue