extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,239 lines (1,018 loc) • 157 kB
JavaScript
/* global expect, xdescribe, Ext, jasmine, spyOn */
(function() {
function makeObservableSuite(isMixin) {
describe(isMixin ? "Ext.mixin.Observable" : "Ext.util.Observable", function() {
var Observable = isMixin ? Ext.mixin.Observable : Ext.util.Observable,
Boss,
boss,
bossConfig,
bossListeners,
bossAskListener,
bossAskFn,
bossFiredFn,
bossFired2Fn,
bossQuitFn,
Employee,
employee,
employeeConfig,
employeeListeners,
employeeBubbleEvents,
employeeAskListener,
employeeAskFn,
employeeFiredListener,
employeeFiredFn,
employeeQuitListener,
employeeQuitFn,
fakeScope;
function makeDefaultListenerScope(o) {
o.resolveListenerScope = function() {
if (!this.defaultScope) {
this.defaultScope = {
meth1: function() {
},
resolveListenerScope: function() {
return null;
}
};
}
return this.defaultScope;
};
}
function spyOnEvent(object, eventName, fn, options) {
var listeners = Ext.apply({}, options),
spy;
listeners[eventName] = fn || Ext.emptyFn;
spy = spyOn(listeners, eventName);
object.addListener(listeners);
return spy
}
beforeEach(function() {
fakeScope = {};
// boss creation
Boss = Ext.extend(Observable, {
constructor: function(conf) {
conf = conf || {};
this.listeners = conf.listeners;
Boss.superclass.constructor.call(this, conf);
},
doSomething: function() {
}
});
bossFiredFn = jasmine.createSpy("bossFiredFn");
bossFired2Fn = jasmine.createSpy("bossFired2Fn");
bossQuitFn = jasmine.createSpy("bossQuitFn");
bossAskFn = jasmine.createSpy("bossAskFn");
bossAskListener = {
fn: bossAskFn,
scope: fakeScope
};
bossListeners = {
ask_salary_augmentation: bossAskListener
};
bossConfig = {
listeners: bossListeners
};
boss = new Boss(bossConfig);
// employee creation
Employee = Ext.extend(Observable, {
constructor: function(conf) {
conf = conf || {};
this.listeners = conf.listeners;
Employee.superclass.constructor.call(this, conf);
},
getBubbleTarget: function() {
return this.boss;
}
});
employeeFiredFn = jasmine.createSpy("employeeFiredFn");
employeeQuitFn = jasmine.createSpy("employeeQuitFn");
employeeAskFn = jasmine.createSpy("employeeAskFn");
employeeFiredListener = {
fn: employeeFiredFn,
scope: fakeScope
};
employeeQuitListener = {
fn: employeeQuitFn,
scope: fakeScope
};
employeeAskListener = {
fn: employeeAskFn,
scope: fakeScope
};
employeeListeners = {
ask_salary_augmentation: employeeAskListener,
fired: employeeFiredListener,
quit: employeeQuitListener
};
employeeBubbleEvents = ['ask_salary_augmentation'];
employeeConfig = {
listeners: employeeListeners,
bubbleEvents: employeeBubbleEvents,
boss: boss
};
// some spies used in constructor
spyOn(Employee.prototype, "on").andCallThrough();
spyOn(Employee.prototype, "addListener").andCallThrough();
spyOn(Employee.prototype, "enableBubble").andCallThrough();
employee = new Employee(employeeConfig);
});
afterEach(function() {
Observable.prototype.fireEventArgs.target = null;
});
describe("constructor", function() {
it("should allow the constructor to be called multiple times", function() {
// In this test a class (Cls) uses two mixins that derive from Observable
// and also mixes in observable itself. The class calls the constructor
// of both mixins and the Observable constructor from its constructor.
// This results in 3 calls to the Observable constructor. Since all 3 of
// these calls are equivalent, only the first one should have any effect.
// Successive calls should not re-initialize anything that was already
// initialized by the first constructor call.
var MixinA = Ext.define(null, {
extend: Observable
}),
MixinB = Ext.define(null, {
extend: Observable
}),
Cls = Ext.define(null, {
mixins: {
mixinA: MixinA,
mixinB: MixinB,
observable: Observable
},
constructor: function(config) {
var initConfig = spyOn(this, 'initConfig').andCallThrough(),
isMixinObservable = Observable.$className === 'Ext.mixin.Observable',
hasListeners, events;
this.mixins.mixinA.constructor.call(this, config);
if (isMixinObservable) {
expect(initConfig).toHaveBeenCalledWith({foo: 'bar'});
} else {
expect(this.foo).toBe('bar');
}
// After the first invocation of the constructor a couple objects
// are created. Cache these so we can make sure that successive
// invocations do not recreate these objects
hasListeners = this.hasListeners;
events = this.events;
this.mixins.observable.constructor.call(this, config);
expect(this.hasListeners).toBe(hasListeners);
expect(this.events).toBe(events);
this.mixins.mixinB.constructor.call(this, config);
expect(this.hasListeners).toBe(hasListeners);
expect(this.events).toBe(events);
if (isMixinObservable) {
expect(initConfig.callCount).toBe(1);
}
}
});
new Cls({foo: 'bar'});
});
});
describe("destroyable", function() {
describe('listeners', function() {
it('should remove the listeners when you destroy the returned Destroyable', function() {
var newBoss = new Boss(),
listenerDestroyable = newBoss.on({
fired: function() {
},
quit: function() {
},
ask_salary_augmentation: function() {
},
destroyable: true
});
expect(newBoss.hasListeners.fired).toEqual(1);
expect(newBoss.hasListeners.quit).toEqual(1);
expect(newBoss.hasListeners.ask_salary_augmentation).toEqual(1);
listenerDestroyable.destroy();
expect(newBoss.hasListeners.fired).toBeUndefined();
expect(newBoss.hasListeners.quit).toBeUndefined();
expect(newBoss.hasListeners.ask_salary_augmentation).toBeUndefined();
});
});
describe('managed listeners', function() {
it('should remove managed listeners when you destroy the returned Destroyable', function() {
var newBoss = new Boss(),
listenerDestroyable = newBoss.mon(newBoss, {
fired: function() {
},
quit: function() {
},
ask_salary_augmentation: function() {
},
destroyable: true
});
expect(newBoss.hasListeners.fired).toEqual(1);
expect(newBoss.hasListeners.quit).toEqual(1);
expect(newBoss.hasListeners.ask_salary_augmentation).toEqual(1);
listenerDestroyable.destroy();
expect(newBoss.hasListeners.fired).toBeUndefined();
expect(newBoss.hasListeners.quit).toBeUndefined();
expect(newBoss.hasListeners.ask_salary_augmentation).toBeUndefined();
});
});
describe('relayers', function() {
it('should remove relayers when you destroy the returned Destroyable', function() {
var newBoss = new Boss(),
newEmployee = new Employee(),
relayers = newBoss.relayEvents(newEmployee, ['fired', 'quit', 'ask_salary_augmentation'], 'minion_'),
quit = 0, fired = 0, ask_salary_augmentation = 0;
newBoss.on({
minion_fired: function() {
fired++;
},
minion_quit: function() {
quit++;
},
minion_ask_salary_augmentation: function() {
ask_salary_augmentation++;
}
});
// Employee's events now have the relayers as listeners
expect(newEmployee.hasListeners.fired).toEqual(1);
expect(newEmployee.hasListeners.quit).toEqual(1);
expect(newEmployee.hasListeners.ask_salary_augmentation).toEqual(1);
// Fire the Employee events which should be relayed through the Boss
newEmployee.fireEvent('fired');
newEmployee.fireEvent('quit');
newEmployee.fireEvent('ask_salary_augmentation');
expect(fired).toEqual(1);
expect(quit).toEqual(1);
expect(ask_salary_augmentation).toEqual(1);
// Destroy the relayers, employee's events now should have no listeners
relayers.destroy();
expect(newEmployee.hasListeners.fired).toBeUndefined();
expect(newEmployee.hasListeners.quit).toBeUndefined();
expect(newEmployee.hasListeners.ask_salary_augmentation).toBeUndefined();
// Fire the Employee events which should **NOT** be relayed through the Boss
// The counters should remain at 1
newEmployee.fireEvent('fired');
newEmployee.fireEvent('quit');
newEmployee.fireEvent('ask_salary_augmentation');
expect(fired).toEqual(1);
expect(quit).toEqual(1);
expect(ask_salary_augmentation).toEqual(1);
});
});
});
describe("instantiation", function() {
describe("config initialization", function() {
if (Observable === Ext.mixin.Observable) {
it("should invoke initConfig", function() {
var Foo = Ext.define(null, {
extend: Observable
});
spyOn(Foo.prototype, 'initConfig');
spyOn(Ext, 'apply');
var foo = new Foo({x: 1});
expect(Foo.prototype.initConfig).toHaveBeenCalledWith({x: 1});
expect(Ext.apply).not.toHaveBeenCalled();
foo.destroy();
});
it("should apply configuration if $applyConfigs is true", function() {
var Foo = Ext.define(null, {
extend: Observable,
$applyConfigs: true
});
spyOn(Foo.prototype, 'initConfig');
spyOn(Ext, 'apply');
var foo = new Foo({x: 1});
expect(Ext.apply).toHaveBeenCalledWith(foo, {x: 1});
expect(Foo.prototype.initConfig).not.toHaveBeenCalled();
foo.destroy();
});
} else {
it("should apply configuration", function() {
var Foo = Ext.define(null, {
extend: Observable
});
spyOn(Foo.prototype, 'initConfig');
spyOn(Ext, 'apply');
var foo = new Foo({x: 1});
expect(Ext.apply).toHaveBeenCalledWith(foo, {x: 1});
expect(Foo.prototype.initConfig).not.toHaveBeenCalled();
foo.destroy();
});
it("should invoke initConfig if $applyConfigs is false", function() {
var Foo = Ext.define(null, {
extend: Observable,
$applyConfigs: false
});
spyOn(Foo.prototype, 'initConfig');
spyOn(Ext, 'apply');
var foo = new Foo({x: 1});
expect(Foo.prototype.initConfig).toHaveBeenCalledWith({x: 1});
expect(Ext.apply).not.toHaveBeenCalled();
foo.destroy();
});
}
});
it("should append event handlers passed in configuration params", function() {
expect(Employee.prototype.addListener).toHaveBeenCalledWith(employeeListeners);
});
it("should delete listeners configuration property", function() {
expect(employee.listeners).toBeNull();
});
it("should enable bubble", function() {
expect(Employee.prototype.enableBubble).toHaveBeenCalledWith(employeeBubbleEvents);
});
});
describe("event name normalization", function() {
var spy, o;
beforeEach(function() {
spy = jasmine.createSpy();
o = new Observable();
});
describe("firing", function() {
it("should match when firing with lower case", function() {
o.on('FOO', spy);
o.fireEvent('foo');
expect(spy).toHaveBeenCalled();
});
it("should match when firing with mixed case", function() {
o.on('foo', spy);
o.fireEvent('FOO');
expect(spy).toHaveBeenCalled();
});
describe("using mon", function() {
var o2;
beforeEach(function() {
o2 = new Observable();
});
it("should match when firing with lower case", function() {
o2.mon(o, 'FOO', spy);
o.fireEvent('foo');
expect(spy).toHaveBeenCalled();
});
it("should match when firing with mixed case", function() {
o2.mon(o, 'foo', spy);
o.fireEvent('FOO');
expect(spy).toHaveBeenCalled();
});
});
});
describe("removing", function() {
it("should match when removing with lower case", function() {
o.on('FOO', spy);
o.un('foo', spy);
o.fireEvent('foo');
expect(spy).not.toHaveBeenCalled();
});
it("should match when removing with mixed case", function() {
o.on('foo', spy);
o.un('FOO', spy);
o.fireEvent('FOO');
expect(spy).not.toHaveBeenCalled();
});
describe("using mon/mun", function() {
var o2;
beforeEach(function() {
o2 = new Observable();
});
it("should match when removing with lower case", function() {
o2.mon(o, 'FOO', spy);
o2.mun(o, 'foo', spy);
o.fireEvent('foo');
expect(spy).not.toHaveBeenCalled();
});
it("should match when removing with mixed case", function() {
o2.mon(o, 'foo', spy);
o2.mun(o, 'FOO', spy);
o.fireEvent('FOO');
expect(spy).not.toHaveBeenCalled();
});
});
});
describe("hasListener(s)", function() {
it("should use lower case for hasListeners", function() {
o.on('FOO', spy);
expect(o.hasListeners.foo).toBe(1);
});
it("should use lower case for hasListener", function() {
o.on('FOO', spy);
expect(o.hasListener('foo')).toBe(true);
});
describe("using mon", function() {
var o2;
beforeEach(function() {
o2 = new Observable();
});
it("should use lower case for hasListeners", function() {
o2.mon(o, 'FOO', spy);
expect(o.hasListeners.foo).toBe(1);
});
it("should use lower case for hasListener", function() {
o2.mon(o, 'FOO', spy);
expect(o.hasListener('foo')).toBe(true);
});
});
});
describe("suspend/resume", function() {
it("should ignore case when asking if an event is suspended", function() {
o.suspendEvent('FOO');
expect(o.isSuspended('foo')).toBe(true);
});
it("should ignore case when resuming events", function() {
o.on('foo', spy);
o.suspendEvent('FOO');
o.fireEvent('foo');
expect(spy).not.toHaveBeenCalled();
o.resumeEvent('FoO');
o.fireEvent('fOo');
expect(spy).toHaveBeenCalled();
});
});
describe("bubbling", function() {
it("should ignore case when bubbling events", function() {
var other = new Observable();
other.on('foo', spy);
o.enableBubble('FOO');
o.getBubbleTarget = function() {
return other;
};
o.fireEvent('foo');
expect(spy).toHaveBeenCalled();
});
});
});
describe("firing events", function() {
describe("without options", function() {
beforeEach(function() {
employee.fireEvent("fired", "I'am fired :s");
});
describe("bubbling", function() {
it("should not fire boss fired event", function() {
expect(bossFiredFn).not.toHaveBeenCalled();
});
});
it("should call the handler only one times", function() {
expect(employeeFiredFn.callCount).toEqual(1);
});
it("should call the handler function with passed arguments", function() {
expect(employeeFiredFn).toHaveBeenCalledWith("I'am fired :s", employeeFiredListener);
});
it("should call the handler function with the correct scope", function() {
expect(employeeFiredFn.calls[0].object).toBe(fakeScope);
});
describe("scope resolution", function() {
describe("with a function reference", function() {
it("should resolve to the instance with scope:'this'", function() {
var spy = jasmine.createSpy();
boss.on('fired', spy, 'this');
boss.fireEvent('fired');
expect(spy.mostRecentCall.object).toBe(boss);
});
it("should throw an error with scope:'controller'", function() {
var spy = jasmine.createSpy();
boss.on('fired', spy, 'controller');
expect(function() {
boss.fireEvent('fired');
}).toThrow();
});
});
describe("with scope: 'this'", function() {
it("resolve to the observable", function() {
boss.on('fired', 'doSomething', 'this');
var spy = spyOn(boss, 'doSomething');
boss.fireEvent('fired');
expect(spy).toHaveBeenCalled();
});
});
describe("with scope: 'controller'", function() {
it("not be able to resolve", function() {
boss.on('fired', 'doSomething', 'controller');
expect(function() {
boss.fireEvent('fired');
}).toThrow(
'scope: "controller" can only be specified on classes that derive from Ext.Component or Ext.Widget'
);
});
});
describe("without default listener scope", function() {
it("should always fire on the passed scope", function() {
var o = {
aMethod: function() {
}
};
var spy = spyOn(o, 'aMethod');
boss.on('fired', 'aMethod', o);
boss.fireEvent('fired');
expect(spy).toHaveBeenCalled();
});
it("should default to the observable", function() {
boss.aMethod = function() {
};
var spy = spyOn(boss, 'aMethod');
boss.on('fired', 'aMethod');
boss.fireEvent('fired');
expect(spy).toHaveBeenCalled();
});
});
describe("with default listener scope", function() {
beforeEach(function() {
makeDefaultListenerScope(boss);
});
it("should favour a passed scope", function() {
var o = {
aMethod: function() {
}
};
var spy = spyOn(o, 'aMethod');
boss.on('fired', 'aMethod', o);
boss.fireEvent('fired');
expect(spy).toHaveBeenCalled();
});
it("should favour a default listener scope over the observable", function() {
var spy = spyOn(boss.resolveListenerScope(), 'meth1');
boss.on('fired', 'meth1');
boss.fireEvent('fired');
expect(spy).toHaveBeenCalled();
});
it("should not cache the listener scope", function() {
var other = {
meth1: function() {
}
}, spy1 = spyOn(boss.resolveListenerScope(), 'meth1'),
spy2 = spyOn(other, 'meth1');
boss.on('fired', 'meth1');
boss.fireEvent('fired');
expect(spy1).toHaveBeenCalled();
expect(spy2).not.toHaveBeenCalled();
spy1.reset();
spy2.reset();
boss.resolveListenerScope = function() {
return other;
};
boss.fireEvent('fired');
expect(spy1).not.toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
});
});
});
});
describe("with options", function() {
describe("single", function() {
var singleFn;
beforeEach(function() {
singleFn = jasmine.createSpy("singleFn");
boss.addListener("singleevent", singleFn, fakeScope, {
single: true
});
boss.fireEvent("singleevent", "single 1");
boss.fireEvent("singleevent", "single 2");
boss.fireEvent("singleevent", "single 3");
});
it("should call the handler only one times", function() {
expect(singleFn.callCount).toEqual(1);
});
it("should call the handler function with passed arguments", function() {
expect(singleFn).toHaveBeenCalledWith("single 1", {
single: true
});
});
it("should call the handler function with the correct scope", function() {
expect(singleFn.calls[0].object).toBe(fakeScope);
});
it("should remove the listener", function() {
expect(boss.hasListener("singleevent")).toBe(false);
});
it("should fire with dynamic scope resoution", function() {
boss = new Boss();
makeDefaultListenerScope(boss);
var spy = spyOn(boss.resolveListenerScope(), 'meth1');
boss.addListener("singleevent", 'meth1', undefined, {
single: true
});
boss.fireEvent("singleevent", "single 1");
boss.fireEvent("singleevent", "single 2");
boss.fireEvent("singleevent", "single 3");
expect(spy.callCount).toBe(1);
});
});
describe("target", function() {
var ct,
callbackFn,
callbackFn2;
beforeEach(function() {
ct = Ext.create('Ext.container.Container', {
items: [{
bubbleEvents: ['add', 'remove'],
xtype: 'container',
itemId: 'foo',
items: [{
bubbleEvents: ['add', 'remove'],
xtype: 'component',
itemId: 'bar'
}, {
bubbleEvents: ['add', 'remove'],
xtype: 'component',
itemId: 'baz'
}]
}]
});
callbackFn = jasmine.createSpy('callbackFn');
callbackFn2 = jasmine.createSpy('callbackFn2');
});
afterEach(function() {
ct.destroy();
});
it("should bubble up to its owner containers when target is not defined", function() {
ct.on(
'remove',
callbackFn
);
ct.getComponent('foo').on(
'remove',
callbackFn2
);
ct.getComponent('foo').remove('bar');
expect(callbackFn).toHaveBeenCalled();
expect(callbackFn2).toHaveBeenCalled();
});
it("should not bubble up to its owner containers when target is defined on a different observable", function() {
ct.on(
'remove',
callbackFn,
ct,
{target: ct}
);
ct.getComponent('foo').on(
'remove',
callbackFn2,
ct,
{target: ct}
);
ct.getComponent('foo').remove('baz');
expect(callbackFn).not.toHaveBeenCalled();
expect(callbackFn2).not.toHaveBeenCalled();
});
it("should not bubble up to its owner container but will bubble up to its ancestor", function() {
ct.on(
'add',
callbackFn
);
ct.getComponent('foo').on(
'add',
callbackFn2,
ct,
{target: ct}
);
ct.getComponent('foo').add({
xtype: 'component',
itemId: 'test'
});
expect(callbackFn).toHaveBeenCalled();
expect(callbackFn2).not.toHaveBeenCalled();
});
it("should fire with dynamic scope resolution", function() {
makeDefaultListenerScope(ct);
var spy = spyOn(ct.resolveListenerScope(), 'meth1');
ct.on('add', 'meth1', undefined, {
target: ct
});
ct.add({
xtype: 'component',
itemId: 'test'
});
expect(spy).toHaveBeenCalled();
});
});
describe("buffer", function() {
var bufferFn;
beforeEach(function() {
bufferFn = jasmine.createSpy("bufferFn");
boss.addListener("bufferevent", bufferFn, fakeScope, {
buffer: 5
});
boss.fireEvent("bufferevent", "buffer 1");
boss.fireEvent("bufferevent", "buffer 2");
boss.fireEvent("bufferevent", "buffer 3");
});
it("should not call handler immediately", function() {
expect(bufferFn).not.toHaveBeenCalled();
});
it("should call the handler only one times after a certain amount of time", function() {
waitsFor(function() {
return bufferFn.callCount === 1;
}, "bufferFn wasn't called");
});
it("should call the handler function with passed arguments coming from the last event firing", function() {
waitsFor(function() {
return bufferFn.callCount === 1;
}, "bufferFn wasn't called");
runs(function() {
expect(bufferFn).toHaveBeenCalledWith("buffer 3", {
buffer: 5
});
});
});
it("should call the handler function with the correct scope", function() {
waitsFor(function() {
return bufferFn.callCount === 1;
}, "bufferFn wasn't called");
runs(function() {
expect(bufferFn.calls[0].object).toBe(fakeScope);
});
});
it("should not remove the listener", function() {
waitsFor(function() {
return bufferFn.callCount === 1;
}, "bufferFn wasn't called");
runs(function() {
expect(boss.hasListener("bufferevent")).toBe(true);
});
});
it("should fire with dynamic scope resolution", function() {
boss = new Boss();
makeDefaultListenerScope(boss);
var spy = spyOn(boss.resolveListenerScope(), 'meth1');
boss.on("bufferevent", 'meth1', undefined, {
buffer: 5
});
boss.fireEvent("bufferevent", "buffer 1");
boss.fireEvent("bufferevent", "buffer 2");
boss.fireEvent("bufferevent", "buffer 3");
waitsFor(function() {
return spy.callCount === 1;
}, "spy wasn't called");
runs(function() {
expect(spy.callCount).toBe(1);
});
});
});
describe("delay", function() {
var delayFn;
beforeEach(function() {
delayFn = jasmine.createSpy("delayFn");
boss.addListener("delayevent", delayFn, fakeScope, {
delay: 5
});
boss.fireEvent("delayevent", "delay");
});
it("should not call handler immediately", function() {
expect(delayFn).not.toHaveBeenCalled();
});
it("should call the handler only one times after a certain amount of time", function() {
waitsFor(function() {
return delayFn.callCount === 1;
}, "delayFn wasn't called");
});
it("should call the handler function with passed arguments", function() {
waitsFor(function() {
return delayFn.callCount === 1;
}, "delayFn wasn't called");
runs(function() {
expect(delayFn).toHaveBeenCalledWith("delay", {
delay: 5
});
});
});
it("should call the handler function with the correct scope", function() {
waitsFor(function() {
return delayFn.callCount === 1;
}, "delayFn wasn't called");
runs(function() {
expect(delayFn.calls[0].object).toBe(fakeScope);
});
});
it("should fire with dynamic scope resolution", function() {
boss = new Boss();
makeDefaultListenerScope(boss);
var spy = spyOn(boss.resolveListenerScope(), 'meth1');
boss.on("delayevent", 'meth1', undefined, {
delay: 5
});
boss.fireEvent("delayevent", "buffer 1");
waitsFor(function() {
return spy.callCount === 1;
}, "spy wasn't called");
runs(function() {
expect(spy).toHaveBeenCalled();
});
});
});
describe("priority", function() {
var a, result;
beforeEach(function() {
Ext.define('A', {
extend: Observable
});
a = new A();
result = [];
});
afterEach(function() {
Ext.undefine('A');
});
it("should call the handlers in priority order", function() {
a.on('foo', function() {
result.push(10);
}, null, {priority: 10});
a.on('foo', function() {
result.push('u1');
}, null);
a.on('foo', function() {
result.push(-7);
}, null, {priority: -7});
a.on('foo', function() {
result.push(0);
}, null, {priority: 0});
a.on('foo', function() {
result.push(5);
}, null, {priority: 5});
a.on('foo', function() {
result.push(-3);
}, null, {priority: -3});
a.on('foo', function() {
result.push('u2');
});
a.fireEvent('foo');
expect(result.join(' ')).toBe('10 5 u1 0 u2 -3 -7');
});
it("should add a 0 priority listener after removal of a positive priority listener, when the listeners array contains negative priority listeners", function() {
// This spec is needed because of the inner workings of the priority
// mechanism. Internally, to avoid excessive looping, it tracks a
// highestNegativePriorityIndex so that when a 0 or undefined priority
// listener is added it can simply be inserted before the listener
// with the highest negative index. This spec ensures the internal
// index gets updated when listeners are removed.
function f10() {
result.push(10);
}
a.on('foo', function() {
result.push('u1');
});
a.on('foo', f10, null, {priority: 10});
a.on('foo', function() {
result.push(-7);
}, null, {priority: -7});
a.on('foo', function() {
result.push(5);
}, null, {priority: 5});
a.un('foo', f10);
a.on('foo', function() {
result.push('u2');
});
a.fireEvent('foo');
expect(result.join(' ')).toBe('5 u1 u2 -7');
});
});
describe("order", function() {
var a, result;
beforeEach(function() {
Ext.define('A', {
extend: Observable
});
a = new A();
result = [];
});
afterEach(function() {
Ext.undefine('A');
});
it("should fire events in the correct order using the order event option", function() {
a.on('foo', function() {
result.push(101);
}, null, {priority: 101});
a.on('foo', function() {
result.push('after');
}, null, {order: 'after'});
a.on('foo', function() {
result.push(-101);
}, null, {priority: -101});
a.on('foo', function() {
result.push('before');
}, null, {order: 'before'});
a.on('foo', function() {
result.push('current');
}, null, {order: 'current'});
a.on('foo', function() {
result.push(0);
}, null, {priority: 0});
a.fireEvent('foo');
expect(result.join(' ')).toBe('101 before current 0 after -101');
});
it("should fire events in the correct order using the order method parameter", function() {
a.on('foo', function() {
result.push(101);
}, null, {priority: 101});
a.on('foo', function() {
result.push('after');
}, null, null, 'after');
a.on('foo', function() {
result.push(-101);
}, null, {priority: -101});
a.on('foo', function() {
result.push('before');
}, null, null, 'before');
a.on('foo', function() {
result.push('current');
}, null, null, 'current');
a.on('foo', function() {
result.push(0);
}, null, {priority: 0});
a.fireEvent('foo');
expect(result.join(' ')).toBe('101 before current 0 after -101');
});
});
});
describe("return value", function() {
var fn1, fn2, fn3, fn4,
fn1Called, fn2Called, fn3Called, fn4Called = false;
beforeEach(function() {
fn1Called = fn2Called = fn3Called = fn4Called = false;
fn1 = function() {
fn1Called = true;
return true;
};
fn2 = function() {
fn2Called = true;
return true;
};
fn3 = function() {
fn3Called = true;
return true;
};
fn4 = function() {
fn4Called = true;
return false;
};
});
afterEach(function() {
fn1 = fn2 = fn3 = fn4 = null;
});
it("should return true if there are no listeners", function() {
employee = new Employee();
expect(employee.fireEvent('quit')).toBe(true);
});
it("should return true if none of the listeners return false", function() {
employee = new Employee();
employee.on('quit', fn1);
employee.on('quit', fn2);
employee.on('quit', fn3);
expect(employee.fireEvent('quit')).toBe(true);
});
it("should return false if any handler returns false", function() {
employee = new Employee();
employee.on('quit', fn1);
employee.on('quit', fn4);
employee.on('quit', fn3);
expect(employee.fireEvent('quit')).toBe(false);
});
it("should stop firing once a listener returns false", function() {
employee = new Employee();
employee.on('quit', fn1);
employee.on('quit', fn4);
employee.on('quit', fn2);
employee.on('quit', fn3);
employee.fireEvent('quit');
expect(fn1Called).toBe(true);
expect(fn4Called).toBe(true);
expect(fn2Called).toBe(false);
expect(fn3Called).toBe(false);
});
});
// https://sencha.jira.com/browse/EXTJS-22353
// Three different but related bugs here, so three suites
describe("during scope destruction", function() {
var employee2, listenerSpy;
beforeEach(function() {
spyOn(Ext, 'raise');
listenerSpy = jasmine.createSpy('listener');
employee2 = new Employee();
employee.on('foo', function() {
employee2.destroy();
});
});
afterEach(function() {
listenerSpy = employee2 = Ext.destroy(employee2);
});
describe("with non-managed listener", function() {
beforeEach(function() {
employee.on('foo', listenerSpy, employee2);
});
it("should not throw an exception", function() {
expect(function() {
employee.fireEvent('foo');
}).not.toThrow();
});
it("should not fire the listener", function() {
employee.fireEvent('foo');
expect(listenerSpy).not.toHaveBeenCalled();
});
it("should remove the listener", function() {
employee.fireEvent('foo');
// The destruction listener above
expect(employee.hasListeners.foo).toBe(1);
});
});
describe("with managed listener", function() {
beforeEach(function() {
employee2.mon(employee, 'foo', listenerSpy, employee2);
});
it("should not throw an exception", function() {
expect(function() {
employee.fireEvent('foo');
}).not.toThrow();
});
it("should not fire the listener", function() {
employee.fireEvent('foo');