// ========================================================================== // Project: SproutCore
- JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // License: Licensed under MIT license (see license.js) // ========================================================================== module(“Function#enhance”);
test(“reopening and enhancing”, function() {
var Klass = SC.Object.extend({ loudly: function(string) { return string + this.get('exclaim'); }, exclaim: "!" }); Klass.reopen({ loudly: function(original, string) { return original(string.toUpperCase()); }.enhance() }); var obj = Klass.create(); equals(obj.loudly("foo"), "FOO!");
});
test(“subclassing and then enhancing the parent”, function() {
var Klass = SC.Object.extend({ loudly: function(string) { return string + this.get('exclaim'); // foo! }, exclaim: "!" }); var obj = Klass.create(); equals(obj.loudly("foo"), "foo!"); Klass.reopen({ loudly: function(original, string) { return original(string.toUpperCase()); // FOO! }.enhance() }); obj = Klass.create(); equals(obj.loudly("foo"), "FOO!"); SubKlass = Klass.extend({ loudly: function(string) { return "ZOMG " + sc_super(); // ZOMG FOO! } }); obj = SubKlass.create(); equals(obj.loudly("foo"), "ZOMG FOO!"); Klass.reopen({ loudly: function(original, string) { return "OHAI: " + original(string); // OHAI: FOO! }.enhance() }); obj = Klass.create(); equals(obj.loudly("foo"), "OHAI: FOO!"); obj = SubKlass.create(); equals(obj.loudly("foo"), "ZOMG OHAI: FOO!");
});
test(“calling sc_super inside a reopened class”, function() {
var Klass = SC.Object.extend({ loudly: function(string) { return string + this.get('exclaim'); }, exclaim: "!" }); Klass.reopen({ loudly: function(original, string) { return original(string.toUpperCase()); }.enhance() }); SubKlass = Klass.extend({}); SubKlass.reopen({ loudly: function(string) { return "ZOMG " + sc_super(); } }); SubKlass.reopen({ loudly: function(original, string) { return "OHAI: " + original(string); }.enhance() }); Klass.reopen({ loudly: function(original, string) { return "HAHA " + original(string); }.enhance() }); var obj = SubKlass.create(); equals(obj.loudly("foo"), "OHAI: ZOMG HAHA FOO!");
});
test(“calling sc_super inside a reopened class, reverse”, function() {
var Klass = SC.Object.extend(); var object = Klass.create({ loudly: function(string) { return sc_super() + "!"; } }); Klass.reopen({ loudly: function(string) { return string.toUpperCase(); } }); equals(object.loudly("foo"), "FOO!");
});
test(“sc_super to a non-method”, function() {
var Klass = SC.Object.extend({ wot: function() { return sc_super(); } }); var object = Klass.create(), error; try { object.wot(); } catch(e) { error = e; } ok(error, "sc_super throws an error if there is no superclass method");
});
test(“sc_super works in enhanced methods”, function() {
var Klass = SC.Object.extend({ loudly: function(string) { return string.toUpperCase(); } }); var SubKlass = Klass.extend({ loudly: function(string) {} }); SubKlass.reopen({ loudly: function(original, string) { return sc_super(); }.enhance() }); var object = SubKlass.create({}); equals("TOM DAAALE IS A FOO", object.loudly("Tom DAAALE is a foo"), "sc_super should work in enhanced methods");
});
// When creating a new instance of a class or extending a class, SproutCore
// keeps a record of the object that calls to sc_super should be looked up // on. // // Calls to sc_super are dynamic, which means that you can modify a class or // superclass of an object at runtime, and sc_super will correctly pick up // those changes. This is especially important for calls to reopen and // enhance. test(“sc_super semantics”, function() {
var rootObject = SC.Object.create({}); ok(rootObject.__sc_super__ === SC.Object.prototype, "SproutCore remembers that new SC.Objects should super to SC.Object.prototype"); var basicObject = new SC.Object(); ok(basicObject.__sc_super__ === SC.Object.prototype, "SproutCore remembers that SC.Objects created by new SC.Object should super to SC.Object.prototype"); var Klass = SC.Object.extend({}); ok(Klass.__sc_super__ === SC.Object.prototype, "SproutCore remembers the original begetted prototype for subclasses"); var object = Klass.create({}); ok(object.__sc_super__ === Klass.prototype, "SproutCore remembers the original prototype for new instances"); var basicSubclassObject = new Klass(); ok(basicSubclassObject.__sc_super__ === Klass.prototype, "SproutCore remembers the original prototype for new instances created with new"); var SubKlass = Klass.extend({}); ok(SubKlass.__sc_super__ === Klass.prototype, "SproutCore remembers the original begetted prototype for custom subclasses"); SubKlass.reopen({}); ok(SubKlass.__sc_super__ === Klass.prototype, "Reopen doesn't break prototype recordkeeping");
});
test(“enhance still works if there is no base method to enhance”, function() {
var enhancer = { weirdName: function(original) { original(); return YES; }.enhance() }; var enhanced = SC.Object.create(enhancer); ok(enhanced.weirdName(), "enhanced function runs with no errors");
});
/**
There was a bug that defining a subclass prior to reopening the parent class and adding a computed property, binding or observer to the parent, caused the subclass not to register the property, binding or observer correctly.
*/ test(“reopening a class that has been subclassed, updates the subclasses properties, bindings and observers”, function() {
var propertyChanged = 0, observerCalled = 0; MyBindable = SC.Object.create({ a: 1, b: 2 }); var MyClass = SC.Object.extend({ property: function() { }.property() }); // Define a subclass prior to the reopen(). var MySubclass = MyClass.extend({ anotherProperty: function() { return "My Own Property"; }.property(), c: 3 }); // Reopen and add a computed property, binding and observer to the parent class MyClass.reopen({ anotherProperty: function() { return "Another Property"; }.property(), yetAnotherProperty: function() { return "Yet Another Property"; }.property('property'), a: 1, aBinding: SC.Binding.oneWay("MyBindable.a"), bObserver: function() { observerCalled++; }.observes('MyBindable.b'), c: 4 }); // Define another subclass after the reopen(). var MyOtherSubclass = MyClass.extend({ stillOneOtherProperty: function() { }.property() }); // Create instances of each for comparison. var myClass = MyClass.create(); myClass.addObserver('yetAnotherProperty', function() { propertyChanged++; }); var mySubclass = MySubclass.create(); mySubclass.addObserver('yetAnotherProperty', function() { propertyChanged++; }); var myOtherSubclass = MyOtherSubclass.create(); myOtherSubclass.addObserver('yetAnotherProperty', function() { propertyChanged++; }); SC.run(function() { myClass.set('property', "foo"); mySubclass.set('property', "foo"); myOtherSubclass.set('property', "foo"); MyBindable.set('a', 2); MyBindable.set('b', 3); }); equals(propertyChanged, 3, "property invalidated thrice"); equals(observerCalled, 3, "fires observer thrice"); equals(myClass.get('a'), 2, "myClass bound value should be 2"); equals(mySubclass.get('a'), 2, "mySubclass bound value should be 2"); equals(myOtherSubclass.get('a'), 2, "myOtherSubclass bound value should be 2"); equals(mySubclass.get('c'), 3, "mySubclass overridden property should still be set"); equals(mySubclass.get('anotherProperty'), "My Own Property", "mySubclass overridden computed property should still exist");
});