sooper
Version:
Simple Object Oriented Programming inheritance and implementation framework for JavaScript.
449 lines (407 loc) • 13.1 kB
JavaScript
describe('sooper', function() {
it('exists', function() {
expect(sooper).toBeDefined();
});
it('has correct properties', function() {
expect(Object.getOwnPropertyNames(sooper).length).toBe(2);
expect(sooper.hasOwnProperty('define')).toBe(true);
expect(sooper.hasOwnProperty('version')).toBe(true);
expect(sooper.version).toBe("2.2.0");
});
describe('defining a class', function() {
describe('with nothing', function() {
it('can be defined', function() {
function createClassWithoutConstructor() {
sooper.define('TestClass', {});
}
expect(createClassWithoutConstructor).not.toThrow();
expect(TestClass).toBeDefined();
expect(TestClass instanceof Function).toBe(true);
expect(TestClass.prototype.constructor).not.toBe(Object);
});
it('can be instantiated', function() {
var test = new TestClass();
expect(test instanceof Object).toBe(true);
expect(test instanceof TestClass).toBe(true);
expect(Object.getOwnPropertyNames(test).length).toBe(0);
expect(Object.keys(test).length).toBe(0);
delete TestClass;
});
it('creates unique constructors', function() {
sooper.define('TestClass1', {
value: 1
});
sooper.define('TestClass2', {
value: 2
});
var t1 = new TestClass1(),
t2 = new TestClass2();
expect(TestClass1.prototype.constructor).not.toBe(TestClass2.prototype.constructor);
expect(t1.value).toBe(1);
expect(t2.value).toBe(2);
delete TestClass1;
delete TestClass2;
});
});
describe('with only a constructor', function() {
it('can be defined', function() {
function createClassWithConstructor() {
sooper.define('TestClass', {
constructor: function(){
this.value = 42;
}
});
}
expect(createClassWithConstructor).not.toThrow();
expect(TestClass).toBeDefined();
});
it('can be instantiated', function() {
var test = new TestClass();
expect(Object.getOwnPropertyNames(test).length).toBe(1);
expect(Object.keys(test).length).toBe(1);
expect(test.value).toBe(42);
delete TestClass;
});
});
describe('with members', function() {
function accessorGetter() { return 'accessor' };
function accessorSetter() {};
it('can be defined', function() {
var testClassConfig = {
constructor: function(value) {
this.memberVar = value;
},
memberVar: 42,
memberFunc: function() {
return this.memberVar;
}
};
Object.defineProperty(testClassConfig, 'data', {
enumerable: false,
configurable: false,
writable: false,
value: 'data'
});
Object.defineProperty(testClassConfig, 'accessor', {
enumerable: false,
configurable: false,
get: accessorGetter,
set: accessorSetter
});
sooper.define('TestClass', testClassConfig);
expect(TestClass).toBeDefined();
});
it('has correct properties', function() {
var proto = TestClass.prototype;
expect(proto.memberVar).toBe(42);
expect(proto.memberFunc instanceof Function).toBe(true);
expect(proto.data).toBe('data');
expect(proto.accessor).toBe('accessor');
expect(proto.hasOwnProperty('memberVar')).toBe(true);
expect(proto.hasOwnProperty('memberFunc')).toBe(true);
expect(proto.hasOwnProperty('data')).toBe(true);
expect(proto.hasOwnProperty('accessor')).toBe(true);
});
it('retains property descriptors', function() {
var proto = TestClass.prototype;
expect(Object.getOwnPropertyDescriptor(proto,'memberVar')).toEqual(jasmine.objectContaining({
writable: true,
enumerable: true,
configurable: true
}));
expect(Object.getOwnPropertyDescriptor(proto,'memberFunc')).toEqual(jasmine.objectContaining({
writable: true,
enumerable: true,
configurable: true
}));
expect(Object.getOwnPropertyDescriptor(proto,'data')).toEqual(jasmine.objectContaining({
enumerable: false,
configurable: false,
writable: false
}));
expect(Object.getOwnPropertyDescriptor(proto,'accessor')).toEqual(jasmine.objectContaining({
enumerable: false,
configurable: false,
get: accessorGetter,
set: accessorSetter
}));
});
it('can be instantiated', function() {
var test = new TestClass(21);
expect(Object.getOwnPropertyNames(test)).toEqual(['memberVar']);
expect(Object.keys(test)).toEqual(['memberVar']);
expect(test.memberVar).toBe(21);
expect(test.memberFunc()).toBe(21);
delete TestClass;
});
});
describe('with a namespace', function() {
describe('single level', function(){
it('is created', function() {
sooper.define('namespace.TestClass1', {
constructor: function() {
this.test = true;
}
});
sooper.define('namespace.TestClass2', {
constructor: function() {
this.test = true;
}
});
expect(namespace).toBeDefined();
expect(namespace.TestClass1).toBeDefined();
expect(namespace.TestClass2).toBeDefined();
expect((new namespace.TestClass1).test).toBe(true);
expect((new namespace.TestClass2).test).toBe(true);
delete namespace;
});
});
describe('multiple levels', function() {
it('is created', function() {
sooper.define('namespace1.namespace2.TestClass', {
constructor: function() {
this.test = true;
}
});
expect(namespace1).toBeDefined();
expect(namespace1.namespace2).toBeDefined();
expect(namespace1.namespace2.TestClass).toBeDefined();
expect((new namespace1.namespace2.TestClass).test).toBe(true);
delete namespace1;
});
});
});
describe('without a namespace', function() {
function createWithoutNamespace () {
return sooper.define({
constructor: function() {},
value: 42
});
}
it('does not throw an error', function() {
expect(createWithoutNamespace).not.toThrow();
});
it('returns the class', function() {
var TestClass = createWithoutNamespace();
expect(TestClass).toBeDefined();
expect(TestClass instanceof Function).toBe(true);
});
it('can be instantiated', function() {
var t = new (createWithoutNamespace())();
expect(t.value).toBe(42);
});
});
describe('with statics', function() {
it('has static properties', function() {
var TestClass = sooper.define({
statics: {
VALUE: 42
}
}),
t = new TestClass();
expect("statics" in t).toBe(false);
expect("VALUE" in t).toBe(false);
expect(TestClass.VALUE).toBe(42);
});
});
});
describe('inheriting a class (basic)', function() {
it('can inherit', function() {
sooper.define('SuperClass', {
superVar: 42,
constructor: function(value) {
if(arguments.length)
this.superVar = value;
},
superFunc: function() { return this.superVar; },
commonFunc: function() { return 1; }
});
sooper.define('TestClass', {
inherits: SuperClass,
subVar: 21,
constructor: function(value) {
SuperClass.apply(this,arguments);
if (arguments.length)
this.subVar = value + 1;
},
subFunc: function() { return this.subVar; },
commonFunc: function() { return 2; }
});
expect(TestClass.prototype.constructor).toBe(SuperClass);
});
it('can instantiate without args', function() {
var test = new TestClass();
expect(test instanceof TestClass);
expect(test instanceof SuperClass);
expect(test.subVar).toBe(21);
expect(test.subFunc()).toBe(21);
expect(test.superVar).toBe(42);
expect(test.superFunc()).toBe(42);
});
it('can instantiate with args', function() {
var test = new TestClass(1);
expect(test.subVar).toBe(2);
expect(test.subFunc()).toBe(2);
expect(test.superVar).toBe(1);
expect(test.superFunc()).toBe(1);
});
it('overrides super class properties', function() {
var test = new TestClass();
expect(test.commonFunc()).toBe(2);
delete TestClass;
delete SuperClass;
});
});
describe('inheriting a class (advanced)', function() {
afterEach(function() {
delete TestClass;
delete SuperClass;
});
it('can call super constructor', function() {
sooper.define('SuperClass', {
constructor: function(value) {
this.testVar1 = value;
}
});
sooper.define('TestClass', {
inherits: SuperClass,
constructor: function(value) {
this.super(value);
this.testVar2 = value*2;
}
});
var test = new TestClass(42);
expect(test.testVar1).toBe(42);
expect(test.testVar2).toBe(84);
});
it('can call super function on super class', function() {
sooper.define('SuperClass', {
testVar: 1,
constructor: function() {},
testFunc: function(value) {
return this.testVar+value;
}
});
sooper.define('TestClass', {
inherits: SuperClass,
constructor: function() {},
testFunc: function me(value) {
return me.super.call(this,value) + 1;
}
});
var test = new TestClass();
expect(test.testFunc(1)).toBe(3);
});
});
describe('implementing interfaces (basic)', function() {
it('can implement a single interface', function() {
sooper.define('TestInterface', {
interfaceVar: 42,
interfaceFunc: function(){}
});
sooper.define('TestClass', {
implements: TestInterface,
constructor: function() {}
});
expect(TestClass.prototype.interfaceVar).toBe(42);
expect(TestClass.prototype.interfaceFunc).toBe(TestInterface.prototype.interfaceFunc);
delete TestClass;
});
it('can implement multiple interfaces', function() {
sooper.define('TestInterface2', {
interface2Var: 21,
interface2Func: function(){}
});
sooper.define('TestClass', {
implements: [TestInterface,TestInterface2]
});
expect(TestClass.prototype.interfaceVar).toBe(42);
expect(TestClass.prototype.interfaceFunc).toBe(TestInterface.prototype.interfaceFunc);
expect(TestClass.prototype.interface2Var).toBe(21);
expect(TestClass.prototype.interface2Func).toBe(TestInterface2.prototype.interface2Func);
});
it('can be instantiated', function() {
var test = new TestClass();
expect(test.interfaceVar).toBe(42);
expect(test.interfaceFunc).toBe(TestInterface.prototype.interfaceFunc);
expect(test.interface2Var).toBe(21);
expect(test.interface2Func).toBe(TestInterface2.prototype.interface2Func);
delete TestClass;
delete TestInterface2;
delete TestInterface;
});
});
describe('implementing interfaces (advanced)', function() {
it('can implement an interface that implements an interface', function() {
sooper.define('TestInterface1', {
interface1Var: 1,
interface1Func: function(){}
});
sooper.define('TestInterface2', {
implements: TestInterface1,
interface2Var: 2,
interface2Func: function(){}
});
sooper.define('TestClass', {
implements: TestInterface2
});
expect(TestClass.prototype.interface1Var).toBe(1);
expect(TestClass.prototype.interface1Func).toBe(TestInterface1.prototype.interface1Func);
expect(TestClass.prototype.interface2Var).toBe(2);
expect(TestClass.prototype.interface2Func).toBe(TestInterface2.prototype.interface2Func);
delete TestClass;
delete TestInterface2;
delete TestInterface1;
});
it('later interfaces override earlier interfaces', function() {
sooper.define('TestInterface1', {
testVar: 1,
testFunc: function(){ return 1; }
});
sooper.define('TestInterface2', {
testVar: 2,
testFunc: function(){ return 2; }
});
sooper.define('TestClass', {
implements: [TestInterface1,TestInterface2]
});
expect(TestClass.prototype.testVar).toBe(2);
expect(TestClass.prototype.testFunc).toBe(TestInterface2.prototype.testFunc);
delete TestClass;
delete TestInterface2;
delete TestInterface1;
});
it('class properties override interface properties', function() {
sooper.define('TestInterface', {
testVar: 1,
testFunc: function(){return 1;}
});
sooper.define('TestClass', {
implements: TestInterface,
testVar: 2,
testFunc: function(){return 2;}
});
var test = new TestClass();
expect(test.testVar).toBe(2);
expect(test.testFunc()).toBe(2);
delete TestClass;
delete TestInterface;
});
it('can call overriden function on interface via super', function() {
sooper.define('TestInterface', {
testVar: 1,
testFunc: function(value) {
return this.testVar+value;
}
});
sooper.define('TestClass', {
implements: TestInterface,
testFunc: function me(value) {
return me.super.call(this,value) + 1;
}
});
var test = new TestClass();
expect(test.testFunc(1)).toBe(3);
});
});
});