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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View 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
)
);
});
});
});

View 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({});
});
});
});

View 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;
});
});
});