extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,231 lines (1,099 loc) • 238 kB
JavaScript
/* global expect, jasmine, Ext, MockAjaxManager, spyOn */
describe("Ext.app.ViewModel", function() {
var viewModel, scheduler, session, spy;
function bindDeepNotify (key, fn, scope) {
var bind = viewModel.bind(key, fn || spy, scope);
bind.deep = true;
viewModel.notify();
return bind;
}
function bindNotify (key, fn, scope) {
var bind = viewModel.bind(key, fn || spy, scope);
viewModel.notify();
return bind;
}
function setNotify (key, value) {
viewModel.set(key, value);
viewModel.notify();
}
function notify() {
viewModel.notify();
}
function reset () {
for (var i = 0, len = arguments.length; i < len; ++i) {
arguments[i].reset();
}
}
function expectArgs (newVal, oldVal) {
if (arguments.length === 1) {
expectArgsForCall(spy.mostRecentCall, newVal);
} else {
expectArgsForCall(spy.mostRecentCall, newVal, oldVal);
}
}
function expectArgsForCall(theCall, newVal, oldVal) {
var args = theCall.args;
expect(args[0]).toBe(newVal);
if (arguments.length > 2) {
expect(args[1]).toBe(oldVal);
}
}
function makeRecord(Type, id, data) {
data = Ext.apply({
id: id
}, data);
return new Type(data, session);
}
function makeSession() {
session = new Ext.data.Session({
scheduler: {
// Make a huge tickDelay, we'll control it by forcing ticks
tickDelay: 9999
}
});
}
function createViewModel(withSession, cfg) {
if (withSession && !session) {
makeSession();
}
viewModel = new Ext.app.ViewModel(Ext.apply({
id: 'rootVM',
session: session
}, cfg));
scheduler = viewModel.getScheduler();
}
function complete(data) {
Ext.Ajax.mockComplete({
status: 200,
responseText: Ext.encode(data)
});
}
function completeNotify(data) {
complete(data);
notify();
}
beforeEach(function() {
Ext.data.Store.prototype.config.asynchronousLoad = false;
Ext.data.Model.schema.setNamespace('spec');
MockAjaxManager.addMethods();
spy = jasmine.createSpy();
});
afterEach(function() {
Ext.data.Store.prototype.config.asynchronousLoad = undefined;
Ext.destroy(viewModel);
Ext.destroy(session);
session = scheduler = spy = viewModel = null;
MockAjaxManager.removeMethods();
Ext.data.Model.schema.clear(true);
});
describe("isReadOnly", function() {
describe("always readOnly bindings", function() {
it("should be true for template bindings", function() {
createViewModel();
var b = viewModel.bind('Hello {foo}', Ext.emptyFn);
expect(b.isReadOnly()).toBe(true);
});
it("should be true for multi bindings", function() {
createViewModel();
var b = viewModel.bind({
a: '{foo}',
b: '{bar}'
}, Ext.emptyFn);
expect(b.isReadOnly()).toBe(true);
});
});
describe("normal bindings", function() {
it("should not be readOnly by default", function() {
createViewModel();
var b = viewModel.bind('{foo}', Ext.emptyFn);
expect(b.isReadOnly()).toBe(false);
});
it("should not be readOnly when options are passed", function() {
createViewModel();
var b = viewModel.bind('{foo}', Ext.emptyFn, null, {
single: true
});
expect(b.isReadOnly()).toBe(false);
});
it("should be readOnly when twoWay is set to false", function() {
createViewModel();
var b = viewModel.bind('{foo}', Ext.emptyFn, null, {
twoWay: false
});
expect(b.isReadOnly()).toBe(true);
});
});
describe("formulas", function() {
it("should be readOnly if there is no set", function() {
createViewModel(false, {
formulas: {
foo: function() {
return 1;
}
}
});
var b = viewModel.bind('{foo}', Ext.emptyFn);
expect(b.isReadOnly()).toBe(true);
});
it("should be readOnly if there is a set but is marked as twoWay: false", function() {
createViewModel(false, {
formulas: {
foo: {
get: function() {
return 1;
},
set: function() {
this.set('x', 1);
}
}
}
});
var b = viewModel.bind('{foo}', Ext.emptyFn, null, {
twoWay: false
});
expect(b.isReadOnly()).toBe(true);
});
it("should not be readOnly if there is a set", function() {
createViewModel(false, {
formulas: {
foo: {
get: function() {
return 1;
},
set: function() {
this.set('x', 1);
}
}
}
});
var b = viewModel.bind('{foo}', Ext.emptyFn);
expect(b.isReadOnly()).toBe(false);
});
});
});
describe("getting/setting values", function() {
beforeEach(function() {
createViewModel();
});
describe("set", function() {
it("should set a root value if the param is an object", function() {
viewModel.set({
foo: {
bar: 1
},
baz: 2
});
expect(viewModel.getData().foo.bar).toBe(1);
expect(viewModel.getData().baz).toBe(2);
});
it("should set an object at a path", function() {
viewModel.set('foo.bar', {
baz: 1
});
expect(viewModel.getData().foo.bar.baz).toBe(1);
});
it("should set a path + primitive", function() {
viewModel.set('foo.bar', 100);
expect(viewModel.getData().foo.bar).toBe(100);
});
it("should be able to set object instances and not descend into them", function() {
var Cls = Ext.define(null, {
foo: 1
});
var o = new Cls();
viewModel.set('obj', o);
expect(viewModel.getData().obj).toBe(o);
});
it('should be able to set a value to undefined even after an unbound stub has been purged', function() {
var child = new Ext.app.ViewModel({
parent: viewModel,
data: {
frob: 2
}
});
// Will delete stubs that have no bindings.
viewModel.doCollect();
expect(function() {
child.set('frob');
}).not.toThrow();
});
});
describe("get", function() {
it("should be able to retrieve a value at the root", function() {
viewModel.set('foo', 1);
expect(viewModel.get('foo')).toBe(1);
});
it("should descend into a path", function() {
viewModel.set({
foo: {
bar: {
baz: 100
}
}
});
expect(viewModel.get('foo.bar.baz')).toBe(100);
});
it("should return null if the value has not presented", function() {
expect(viewModel.get('something')).toBeNull();
});
});
});
describe("bind/set for non records/stores", function() {
beforeEach(function() {
createViewModel();
});
function createSuite(bindFirst) {
function run(bindFn, setFn) {
if (bindFirst) {
bindFn();
setFn();
} else {
setFn();
bindFn();
}
}
describe(bindFirst ? "bind before set" : "set before bind", function() {
describe("setting simple value types", function() {
it("should set a number", function() {
run(function() {
bindNotify('{age}', spy);
}, function() {
setNotify('age', 3);
});
expectArgs(3, undefined);
});
it("should set a string", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('name', 'Kenneth');
});
expectArgs('Kenneth', undefined);
});
it("should set a bool", function() {
run(function() {
bindNotify('{active}', spy);
}, function() {
setNotify('active', true);
});
expectArgs(true, undefined);
});
it("should set an array", function() {
var arr = [18, 22, 13];
run(function() {
bindNotify('{scores}', spy);
}, function() {
setNotify('scores', arr);
});
expectArgs(arr, undefined);
});
it("should set a date", function() {
var d = new Date(1980, 0, 1);
run(function() {
bindNotify('{dob}', spy);
}, function() {
setNotify('dob', d);
});
expectArgs(d, undefined);
});
it("should set an object instance", function() {
var map = new Ext.util.HashMap();
run(function() {
bindNotify('{myMap}', spy);
}, function() {
setNotify('myMap', map);
});
expectArgs(map, undefined);
});
});
describe("using bind options", function() {
it("should set a value using bindTo", function() {
run(function() {
bindNotify({
bindTo: '{age}'
}, spy);
}, function() {
setNotify('age', 3);
setNotify('age', 5);
});
if (bindFirst) {
expectArgsForCall(spy.calls[0], 3, undefined);
expectArgsForCall(spy.calls[1], 5, 3);
} else {
expectArgs(5, undefined);
}
});
it("should set the value once when using single: true", function() {
run(function() {
bindNotify({
bindTo: '{age}',
single: true
}, spy);
}, function() {
setNotify('age', 3);
setNotify('age', 5);
});
expect(spy.callCount).toBe(1);
if (bindFirst) {
expectArgs(3, undefined);
} else {
expectArgs(5, undefined);
}
});
it("should bind deep", function() {
run(function() {
bindNotify({
bindTo: '{foo}',
deep: true
}, spy);
}, function() {
setNotify({
foo: {
bar: 1
}
});
setNotify('foo.bar', 2);
});
if (bindFirst) {
expect(spy.callCount).toBe(2);
} else {
expect(spy.mostRecentCall.args[0]).toEqual({
bar: 2
});
}
});
});
describe("setting objects", function() {
it("should set to the root if there's no name", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('', {
name: 'Bar'
});
});
expectArgs('Bar', undefined);
});
it("should be able to set simple nested properties", function() {
run(function() {
bindNotify('{user.name}', spy);
}, function() {
setNotify('user', {
name: 'Foo'
});
});
expectArgs('Foo', undefined);
});
it("should set deeply nested properties", function() {
run(function() {
bindNotify('{a.b.c.d.e.f.g}', spy);
}, function() {
setNotify('a', {
b: {
c: {
d: {
e: {
f: {
g: 'val'
}
}
}
}
}
});
});
expectArgs('val', undefined);
});
it("should be able to set mixes of values/objects", function() {
var city = jasmine.createSpy();
run(function() {
viewModel.bind('{user.name}', spy);
viewModel.bind('{user.address.city}', city);
notify();
}, function() {
setNotify('user', {
name: 'Foo',
address: {
city: 'Paris'
}
});
});
expectArgs('Foo', undefined);
expectArgsForCall(city.mostRecentCall, 'Paris');
});
});
describe("callback settings", function() {
it("should pass the old and new value", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('name', 'Foo');
setNotify('name', 'Bar');
});
if (bindFirst) {
expectArgsForCall(spy.calls[0], 'Foo', undefined);
expectArgsForCall(spy.calls[1], 'Bar', 'Foo');
} else {
expectArgs('Bar', undefined);
}
});
it("should default the scope to the session", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('name', 'X');
});
expect(spy.mostRecentCall.object).toBe(viewModel);
});
it("should use the passed scope", function() {
var o = {};
run(function() {
bindNotify('{name}', spy, o);
}, function() {
setNotify('name', 'X');
});
expect(spy.mostRecentCall.object).toBe(o);
});
});
describe("timing of callbacks", function() {
it("should not trigger the callback if the value doesn't change", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('name', 'Foo');
});
spy.reset();
setNotify('name', 'Foo');
expect(spy).not.toHaveBeenCalled();
});
it("should not trigger any parent nodes if the leaf value doesn't change", function() {
var inner = jasmine.createSpy();
run(function() {
viewModel.bind('{foo}', spy);
viewModel.bind('{foo.bar}', inner);
}, function() {
viewModel.set('foo.bar.baz.x', 'Foo');
});
notify();
reset(spy, inner);
setNotify('foo.bar.baz.x', 'Foo');
expect(spy).not.toHaveBeenCalled();
expect(inner).not.toHaveBeenCalled();
});
it("should be able to bind twice to the same stub", function() {
var other = jasmine.createSpy();
run(function() {
bindNotify('{name}', spy);
bindNotify('{name}', other);
}, function() {
setNotify('name', 'A');
});
expectArgsForCall(spy.mostRecentCall, 'A', undefined);
expectArgsForCall(other.mostRecentCall, 'A', undefined);
});
it("should trigger a new binding when there is a set pending", function() {
var other = jasmine.createSpy();
run(function() {
bindNotify('{name}', spy);
}, function() {
viewModel.set('name', 'A');
});
bindNotify('{name}', other);
expect(other).toHaveBeenCalled();
});
it("should only fire a single callback inside the timer resolution", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
viewModel.set('name', 'A');
viewModel.set('name', 'B');
viewModel.set('name', 'C');
viewModel.set('name', 'D');
notify();
});
expect(spy.callCount).toBe(1);
expectArgs('D', undefined);
});
it("should only pass the last value since the last fired change", function() {
run(function() {
bindNotify('{name}', spy);
}, function() {
setNotify('name', 'A');
});
viewModel.set('name', 'B');
viewModel.set('name', 'C');
viewModel.set('name', 'D');
viewModel.set('name', 'E');
notify();
expectArgs('E', 'A');
});
// Tests specifically for bind/data first
if (bindFirst) {
it("should not trigger the binding initially if a value is not set", function() {
bindNotify('{name}', spy);
expect(spy).not.toHaveBeenCalled();
});
it("should suspend the initial binding if the value is set within the tick window", function() {
viewModel.bind('{name}', spy);
setNotify('name', 'Foo');
expectArgs('Foo', undefined);
});
} else {
it("should trigger the binding initially if a value exists", function() {
viewModel.set('name', 'Foo');
bindNotify('{name}', spy);
expect(spy).toHaveBeenCalled();
expectArgs('Foo', undefined);
});
}
});
describe("binding on nested values", function() {
it("should trigger a new long chain binding", function() {
run(function() {
bindNotify('{user.address.city}', spy);
}, function() {
setNotify('user.address.city', 'Sydney');
});
expectArgs('Sydney', undefined);
});
it("should trigger a deep parent binding when a child changes", function() {
var city = jasmine.createSpy(),
address = jasmine.createSpy();
run(function() {
bindNotify('{user.address.city}', city);
bindDeepNotify('{user.address}', address);
}, function() {
setNotify('user.address.city', 'Berlin');
});
expectArgsForCall(city.mostRecentCall, 'Berlin', undefined);
expect(address.mostRecentCall.args[0]).toEqual({
city: 'Berlin'
});
});
it("should trigger all deep parent bindings when a child changes", function() {
var city = jasmine.createSpy(),
address = jasmine.createSpy(),
user = jasmine.createSpy();
run(function() {
bindNotify('{user.address.city}', city);
bindDeepNotify('{user.address}', address);
bindDeepNotify('{user}', user);
}, function() {
setNotify('user.address.city', 'Jakarta');
});
expect(city).toHaveBeenCalled();
expect(address).toHaveBeenCalled();
expect(user).toHaveBeenCalled();
});
it("should trigger parent bindings even if a node in the hierarchy is skipped", function() {
var city = jasmine.createSpy(),
user = jasmine.createSpy();
run(function() {
bindNotify('{user.address.city}', city);
bindDeepNotify('{user}', user);
}, function() {
setNotify('user.address.city', 'London');
});
expect(city).toHaveBeenCalled();
expect(user).toHaveBeenCalled();
});
it("should only trigger the parent binding once if several direct children change", function() {
run(function() {
bindDeepNotify('{user.address}', spy);
}, function() {
viewModel.set('user.address.street', '1 Foo St');
viewModel.set('user.address.city', 'Moscow');
viewModel.set('user.address.zip', 12345);
viewModel.set('user.address.country', 'Russia');
notify();
});
expect(spy.callCount).toBe(1);
});
it("should only trigger the parent once even if several indirect children change", function() {
run(function() {
bindDeepNotify('{user}', spy);
}, function() {
viewModel.set('user.homeAddress.street', 'Foo');
viewModel.set('user.homeAddress.city', 'Florida');
viewModel.set('user.postalAddress.street', 'Bar');
viewModel.set('user.postalAddress.city', 'Baltimore');
notify();
});
expect(spy.callCount).toBe(1);
});
describe("modifying hierarchies", function() {
function getHierarchy(val) {
return {
foo: {
bar: {
baz: {
xxx: val
}
}
}
};
}
it("should trigger changes on the children when hierarchy is overwritten with a primitive", function() {
var xxx = jasmine.createSpy(),
baz = jasmine.createSpy(),
bar = jasmine.createSpy();
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', xxx);
viewModel.bind('{foo.bar.baz}', baz);
viewModel.bind('{foo.bar}', bar);
notify();
}, function() {
setNotify('foo.bar.baz.xxx', 1);
});
reset(xxx, baz, bar);
setNotify('foo', 1);
expect(xxx).toHaveBeenCalled();
expect(baz).toHaveBeenCalled();
expect(bar).toHaveBeenCalled();
});
it("should trigger changes on the children when hierarchy is overwritten with null", function() {
run(function() {
viewModel.bind('{foo.bar}', spy);
notify();
}, function() {
viewModel.set({
foo: {
bar: 1
}
});
notify();
});
spy.reset();
setNotify('foo', null);
expectArgs(null, 1);
});
it("should set the child value correctly when changing a hierarchy in a single tick", function() {
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
}, function() {
viewModel.set(getHierarchy(123));
viewModel.set(getHierarchy(456));
viewModel.set(getHierarchy(789));
});
notify();
expect(spy.callCount).toBe(1);
expectArgs(789);
});
it("should set the child value correctly when changing a hierarchy over multiple ticks", function() {
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
}, function() {
viewModel.set(getHierarchy(123));
});
notify();
expectArgs(123);
viewModel.set(getHierarchy(456));
notify();
expectArgs(456);
viewModel.set(getHierarchy(789));
notify();
expectArgs(789);
});
it("should set the child value correctly when overwriting a hierarchy in a single tick", function() {
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
}, function() {
viewModel.set(getHierarchy(123));
viewModel.set(getHierarchy(456));
viewModel.set({
foo: null
});
});
notify();
if (bindFirst) {
expect(spy.callCount).toBe(1);
expectArgs(null, undefined);
} else {
expect(spy).not.toHaveBeenCalled();
}
});
it("should set the child value correctly when overwriting a hierarchy over multiple ticks", function() {
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
}, function() {
viewModel.set(getHierarchy(123));
});
notify();
expectArgs(123);
viewModel.set(getHierarchy(456));
notify();
expectArgs(456);
viewModel.set({
foo: null
});
notify();
expectArgs(null);
});
it("should be able to expand a primitive into a hierarchy", function() {
var xxx = jasmine.createSpy(),
baz = jasmine.createSpy(),
bar = jasmine.createSpy();
run(function() {
viewModel.bind('{foo.bar.baz.xxx}', xxx, null, {deep: true});
viewModel.bind('{foo.bar.baz}', baz, null, {deep: true});
viewModel.bind('{foo.bar}', bar, null, {deep: true});
}, function() {
viewModel.set('foo', 1);
});
notify();
reset(xxx, baz, bar);
setNotify('foo.bar.baz.xxx', 1);
expect(xxx).toHaveBeenCalled();
expect(baz).toHaveBeenCalled();
expect(bar).toHaveBeenCalled();
});
it("should be able to expand an existing object path", function() {
run(function() {
bindNotify('{foo.bar.baz.xxx}', spy);
}, function() {
viewModel.set({
foo: null
});
});
notify();
setNotify('foo.bar.baz.xxx', 1);
expect(spy.callCount).toBe(1);
expectArgs(1, undefined);
});
if (bindFirst) {
it("should set the child value correctly when expanding a hierarchy in a single tick", function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
notify();
viewModel.set({
foo: null
});
viewModel.set({
foo: {
bar: null
}
});
viewModel.set({
foo: {
bar: {
baz: null
}
}
});
viewModel.set({
foo: {
bar: {
baz: {
xxx: 100
}
}
}
});
notify();
expect(spy.callCount).toBe(1);
expectArgs(100);
});
it("should set the child value correctly when expanding a hierarchy over multiple ticks", function() {
viewModel.bind('{foo.bar.baz.xxx}', spy);
notify();
viewModel.set({
foo: null
});
notify();
expect(spy).not.toHaveBeenCalled();
viewModel.set({
foo: {
bar: null
}
});
notify();
expect(spy).not.toHaveBeenCalled();
viewModel.set({
foo: {
bar: {
baz: null
}
}
});
notify();
expect(spy).not.toHaveBeenCalled();
viewModel.set({
foo: {
bar: {
baz: {
xxx: 100
}
}
}
});
notify();
expectArgs(100);
viewModel.set({
foo: null
});
notify();
// Now that we have a value loaded for xxx, it should publish as null
expectArgs(null);
});
}
});
});
});
}
createSuite(false);
createSuite(true);
describe("data types", function() {
describe("dates", function() {
it("should change when setting an initial date", function() {
var d = new Date(2010, 0, 1);
bindNotify('{val}', spy);
setNotify('val', d);
expectArgs(d, undefined);
});
it("should change when setting a new date", function() {
var d1 = new Date(2010, 0, 1),
d2 = new Date(2000, 3, 15);
setNotify('val', d1);
bindNotify('{val}', spy);
spy.reset();
setNotify('val', d2);
expectArgs(d2, d1);
});
it("should not change when setting the same date with a different reference", function() {
var d1 = new Date(2010, 0, 1),
d2 = new Date(2010, 0, 1);
setNotify('val', d1);
bindNotify('{val}', spy);
spy.reset();
setNotify('val', d2);
expect(spy).not.toHaveBeenCalled();
});
});
});
describe("firing order", function() {
it("should fire children before parents", function() {
var values = [];
viewModel.bind('{address}', function (v) {
values.push('address: ' + Ext.encode(v));
}).deep = true;
viewModel.bind('{address.city}', function (v) {
values.push('address.city: ' + v);
});
notify();
expect(values).toEqual([]);
viewModel.set('address.city', 'Melbourne');
notify();
expect(values[0]).toBe('address.city: Melbourne');
expect(values[1]).toBe('address: {"city":"Melbourne"}');
});
it("should fire a single binding at the depth of it's stub", function() {
setNotify('foo.bar.baz.x', 1);
var values = [],
adder = function(arg1) {
values.push(arg1);
};
viewModel.bind('{foo.bar.baz.x}', adder);
viewModel.bind('{foo.bar.y}', adder);
viewModel.set('foo.bar.y', 3);
viewModel.set('foo.bar.baz.x', 2);
notify();
expect(values[0]).toBe(2);
expect(values[1]).toBe(3);
});
it("should fire complex hierarchies in depth order", function() {
var data = {
key1: {
key11: {
key111: {
key1111: 'a', // d=4
key1112: 'b' // d=4
},
key112: 'c' // d=3
},
key12: {
key121: 'd', // d=3
key122: 'e' // d=3
}
},
key2: {
key21: {
key211: 'f' // d=3
},
key22: {
key221: {
key2211: {
key22111: 'g' // d=5
}
},
key222: {
key2221: 'h' // d=4
}
},
key23: {
key231: 'i' // d=3
}
},
key3: {
key31: 'j', // d=2
key32: {
key321: 'k' // d=3
},
key33: {
key331: {
key3311: 'l' // d=4
},
key332: 'm' // d=3
}
},
key4: {
key41: 'n' // d=2
},
key5: 'o', // d=1
key6: {
key61: {
key611: {
key6111: {
key61111: {
key611111: {
key6111111: 'p' // d=7
},
key611112: 'q' // d=6
}
}
},
key612: {
key6121: {
key61211: {
key61211: 'r' // d=6
}
}
},
key613: {
key6131: {
key61311: {
key613111: {
key6131111: 's' // d=7
}
}
}
}
}
}
};
var map = {};
var items = [];
var entryLog = [];
var valueLog = [];
function buildMap (value, parent, path) {
var entry = {
id: items.length + 1,
path: path,
parent: parent,
value: value
};
items.push(map[path] = entry);
if (path) {
viewModel.bind('{' + path + '}', function (v) {
entryLog.push(entry);
valueLog.push(v);
// We can say for certain that none of our parent objects
// should have called back at this time.
for (var p = parent; p; p = p.parent) {
expect(Ext.Array.contains(entryLog, p)).toBe(false);
}
});
}
if (value && value.constructor === Object) {
var subPath = path ? path + '.' : '';
Ext.Object.each(value, function (name, v) {
buildMap(v, entry, subPath + name);
});
}
return entry;
}
var root = buildMap(data, null, ''),
prefix, i;
notify();
for (i = 0; i < valueLog.length; ++i) {
prefix = entryLog[i].path + '=';
expect(prefix + valueLog[i]).toEqual(prefix + 'null');
}
entryLog.length = valueLog.length = 0;
setNotify('', data);
notify();
for (i = 0; i < valueLog.length; ++i) {
// Each delivered value should preserve the references we passed in
// for this case.
expect(valueLog[i]).toBe(entryLog[i].value);
}
});
});
});
describe("parsing formulas", function () {
var vm;
function getFormula (name) {
var stub = vm.getStub(name);
return stub.formula;
}
function getExpressions (name) {
var formula = getFormula(name),
expressions = Ext.apply({}, formula.get.$expressions);
delete expressions.$literal;
return Ext.Object.getKeys(expressions);
}
beforeEach(function() {
createViewModel();
});
afterEach(function () {
vm.destroy();
vm = null;
});
describe("simple formulas", function () {
it("should recognize property access", function () {
vm = new Ext.app.ViewModel({
formulas: {
foo: function (get) {
return get('x.y') + get('z');
}
}
});
var expressions = getExpressions('foo');
expect(expressions).toEqual(['x.y', 'z']);
});
it("should ignore method calls", function () {
vm = new Ext.app.ViewModel({
formulas: {
foo: function (get) {
return get('x.y').substring(1) + get('z').toLowerCase();
}
}
});
var expressions = getExpressions('foo');
expect(expressions).toEqual(['x.y', 'z']);
});
it("should recognize data as method parameters", function () {
vm = new Ext.app.ViewModel({
formulas: {
foo: function (get) {
return this.foo(get('x')+get('y.z'));
}
}
});
var expressions = getExpressions('foo');
expect(expressions).toEqual(['x', 'y.z']);
});
it("should ignore data used in suffix expression", function () {
vm = new Ext.app.ViewModel({
formulas: {
foo: function (get) {
return this.get.foo(get('x') + get('y.z'));
}
}
});
var expressions = getExpressions('foo');
expect(expressions).toEqual(['x', 'y.z']);
});
});
describe("formula config objects", function () {
it("should recognize property access", function () {
vm = new Ext.app.ViewModel({
formulas: {