@samitouri / QOS-React-2 / commits / c9ecbaccb3
Fork 0

The library for web and native user interfaces.

Use React.autoBind by default.

Per our discussion - this is the general approach we'd like to take for the public facing API. var MyComponent = React.createClass({ render: function() { return <div onClick={this.myCallback} />; }, myCallback: function() { } });

CommitSyncScript committed Jun 24, 2013 at 16:09 UTC c9ecbaccb365ba39bd8839f61ae245cdc295317b
6 files changed +217 -195
src/core/ReactCompositeComponent.js
+112 -105
@@ -28,6 +28,17 @@ var keyMirror = require('keyMirror');
28 var merge = require('merge');
29 var mixInto = require('mixInto');
30
31 +var invokedBeforeMount = function() {
32 + invariant(
33 + false,
34 + 'You have invoked a method that is automatically bound, before the ' +
35 + 'instance has been mounted. There is nothing conceptually wrong with ' +
36 + 'this, but since this method will be replaced with a new version once ' +
37 + 'the component is mounted - you should be aware of the fact that this ' +
38 + 'method will soon be replaced.'
39 + );
40 +};
41 +
42 /**
43 * Policies that describe methods in `ReactCompositeComponentInterface`.
44 */
@@ -283,6 +294,47 @@ var RESERVED_SPEC_KEYS = {
294 }
295 };
296
297 +function validateMethodOverride(proto, name) {
298 + var specPolicy = ReactCompositeComponentInterface[name];
299 +
300 + // Disallow overriding of base class methods unless explicitly allowed.
301 + if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
302 + invariant(
303 + specPolicy === SpecPolicy.OVERRIDE_BASE,
304 + 'ReactCompositeComponentInterface: You are attempting to override ' +
305 + '`%s` from your class specification. Ensure that your method names ' +
306 + 'do not overlap with React methods.',
307 + name
308 + );
309 + }
310 +
311 + // Disallow defining methods more than once unless explicitly allowed.
312 + if (proto.hasOwnProperty(name)) {
313 + invariant(
314 + specPolicy === SpecPolicy.DEFINE_MANY,
315 + 'ReactCompositeComponentInterface: You are attempting to define ' +
316 + '`%s` on your component more than once. This conflict may be due ' +
317 + 'to a mixin.',
318 + name
319 + );
320 + }
321 +}
322 +
323 +
324 +function validateLifeCycleOnReplaceState(instance) {
325 + var compositeLifeCycleState = instance._compositeLifeCycleState;
326 + invariant(
327 + instance.isMounted(),
328 + 'replaceState(...): Can only update a mounted component.'
329 + );
330 + invariant(
331 + compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
332 + compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
333 + 'replaceState(...): Cannot update while unmounting component or during ' +
334 + 'an existing state transition (such as within `render`).'
335 + );
336 +}
337 +
338 /**
339 * Custom version of `mixInto` which handles policy validation and reserved
340 * specification keys when building `ReactCompositeComponent` classses.
@@ -290,58 +342,44 @@ var RESERVED_SPEC_KEYS = {
342 function mixSpecIntoComponent(Constructor, spec) {
343 var proto = Constructor.prototype;
344 for (var name in spec) {
293 - if (!spec.hasOwnProperty(name)) {
294 - continue;
295 - }
345 var property = spec[name];
297 - var specPolicy = ReactCompositeComponentInterface[name];
298 -
299 - // Disallow overriding of base class methods unless explicitly allowed.
300 - if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
301 - invariant(
302 - specPolicy === SpecPolicy.OVERRIDE_BASE,
303 - 'ReactCompositeComponentInterface: You are attempting to override ' +
304 - '`%s` from your class specification. Ensure that your method names ' +
305 - 'do not overlap with React methods.',
306 - name
307 - );
308 - }
309 -
310 - // Disallow using `React.autoBind` on internal methods.
311 - if (specPolicy != null) {
312 - invariant(
313 - !property || !property.__reactAutoBind,
314 - 'ReactCompositeComponentInterface: You are attempting to use ' +
315 - '`React.autoBind` on `%s`, a method that is internal to React.' +
316 - 'Internal methods are called with the component as the context.',
317 - name
318 - );
319 - }
320 -
321 - // Disallow defining methods more than once unless explicitly allowed.
322 - if (proto.hasOwnProperty(name)) {
323 - invariant(
324 - specPolicy === SpecPolicy.DEFINE_MANY,
325 - 'ReactCompositeComponentInterface: You are attempting to define ' +
326 - '`%s` on your component more than once. This conflict may be due ' +
327 - 'to a mixin.',
328 - name
329 - );
346 + if (!spec.hasOwnProperty(name) || !property) {
347 + continue;
348 }
349 + validateMethodOverride(proto, name);
350
351 if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
352 RESERVED_SPEC_KEYS[name](Constructor, property);
334 - } else if (property && property.__reactAutoBind) {
335 - if (!proto.__reactAutoBindMap) {
336 - proto.__reactAutoBindMap = {};
337 - }
338 - proto.__reactAutoBindMap[name] = property.__reactAutoBind;
339 - } else if (proto.hasOwnProperty(name)) {
340 - // For methods which are defined more than once, call the existing methods
341 - // before calling the new property.
342 - proto[name] = createChainedFunction(proto[name], property);
353 } else {
344 - proto[name] = property;
354 + // Setup methods on prototype:
355 + // The following member methods should not be automatically bound:
356 + // 1. Expected ReactCompositeComponent methods (in the "interface").
357 + // 2. Overridden methods (that were mixed in).
358 + var isCompositeComponentMethod = name in ReactCompositeComponentInterface;
359 + var isInherited = name in proto;
360 + var markedDontBind = property.__reactDontBind;
361 + var isFunction = typeof property === 'function';
362 + var shouldAutoBind =
363 + isFunction &&
364 + !isCompositeComponentMethod &&
365 + !isInherited &&
366 + !markedDontBind;
367 +
368 + if (shouldAutoBind) {
369 + if (!proto.__reactAutoBindMap) {
370 + proto.__reactAutoBindMap = {};
371 + }
372 + proto.__reactAutoBindMap[name] = property;
373 + proto[name] = invokedBeforeMount;
374 + } else {
375 + if (isInherited) {
376 + // For methods which are defined more than once, call the existing
377 + // methods before calling the new property.
378 + proto[name] = createChainedFunction(proto[name], property);
379 + } else {
380 + proto[name] = property;
381 + }
382 + }
383 }
384 }
385 }
@@ -370,7 +408,26 @@ function createChainedFunction(one, two) {
408 * `this._compositeLifeCycleState` (which can be null).
409 *
410 * This is different from the life cycle state maintained by `ReactComponent` in
373 - * `this._lifeCycleState`.
411 + * `this._lifeCycleState`. The following diagram shows how the states overlap in
412 + * time. There are times when the CompositeLifeCycle is null - at those times it
413 + * is only meaningful to look at ComponentLifeCycle alone.
414 + *
415 + * Top Row: ReactComponent.ComponentLifeCycle
416 + * Low Row: ReactComponent.CompositeLifeCycle
417 + *
418 + * +-------+------------------------------------------------------+--------+
419 + * | UN | MOUNTED | UN |
420 + * |MOUNTED| | MOUNTED|
421 + * +-------+------------------------------------------------------+--------+
422 + * | ^--------+ +------+ +------+ +------+ +--------^ |
423 + * | | | | | | | | | | | |
424 + * | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 |
425 + * | | | |PROPS | | PROPS| | STATE| |MOUNTING| |
426 + * | | | | | | | | | | | |
427 + * | | | | | | | | | | | |
428 + * | +--------+ +------+ +------+ +------+ +--------+ |
429 + * | | | |
430 + * +-------+------------------------------------------------------+--------+
431 */
432 var CompositeLifeCycle = keyMirror({
433 /**
@@ -429,11 +486,7 @@ var ReactCompositeComponentMixin = {
486 */
487 mountComponent: function(rootID, transaction) {
488 ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
432 -
433 - // Unset `this._lifeCycleState` until after this method is finished.
434 - this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
489 this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
436 -
490 this._processProps(this.props);
491
492 if (this.__reactAutoBindMap) {
@@ -459,15 +512,11 @@ var ReactCompositeComponentMixin = {
512
513 // Done with mounting, `setState` will now trigger UI changes.
514 this._compositeLifeCycleState = null;
462 - this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
463 -
464 - var html = this._renderedComponent.mountComponent(rootID, transaction);
465 -
515 + var markup = this._renderedComponent.mountComponent(rootID, transaction);
516 if (this.componentDidMount) {
517 transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
518 }
469 -
470 - return html;
519 + return markup;
520 },
521
522 /**
@@ -551,18 +600,7 @@ var ReactCompositeComponentMixin = {
600 */
601 replaceState: function(completeState) {
602 var compositeLifeCycleState = this._compositeLifeCycleState;
554 - invariant(
555 - this.isMounted() ||
556 - compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
557 - 'replaceState(...): Can only update a mounted (or mounting) component.'
558 - );
559 - invariant(
560 - compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
561 - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
562 - 'replaceState(...): Cannot update while unmounting component or during ' +
563 - 'an existing state transition (such as within `render`).'
564 - );
565 -
603 + validateLifeCycleOnReplaceState.call(null, this);
604 this._pendingState = completeState;
605
606 // Do not trigger a state transition if we are in the middle of mounting or
@@ -583,7 +621,6 @@ var ReactCompositeComponentMixin = {
621 transaction
622 );
623 ReactComponent.ReactReconcileTransaction.release(transaction);
586 -
624 this._compositeLifeCycleState = null;
625 }
626 },
@@ -777,25 +814,12 @@ var ReactCompositeComponentMixin = {
814 */
815 _bindAutoBindMethod: function(method) {
816 var component = this;
780 - var hasWarned = false;
817 function autoBound(a, b, c, d, e, tooMany) {
818 invariant(
819 typeof tooMany === 'undefined',
820 'React.autoBind(...): Methods can only take a maximum of 5 arguments.'
821 );
786 - if (component._lifeCycleState === ReactComponent.LifeCycle.MOUNTED) {
787 - return method.call(component, a, b, c, d, e);
788 - } else if (!hasWarned) {
789 - hasWarned = true;
790 - if (__DEV__) {
791 - console.warn(
792 - 'React.autoBind(...): Attempted to invoke an auto-bound method ' +
793 - 'on an unmounted instance of `%s`. You either have a memory leak ' +
794 - 'or an event handler that is being run after unmounting.',
795 - component.constructor.displayName || 'ReactCompositeComponent'
796 - );
797 - }
798 - }
822 + return method.call(component, a, b, c, d, e);
823 }
824 return autoBound;
825 }
@@ -850,33 +874,16 @@ var ReactCompositeComponent = {
874 },
875
876 /**
853 - * Marks the provided method to be automatically bound to the component.
854 - * This means the method's context will always be the component.
855 - *
856 - * React.createClass({
857 - * handleClick: React.autoBind(function() {
858 - * this.setState({jumping: true});
859 - * }),
860 - * render: function() {
861 - * return <a onClick={this.handleClick}>Jump</a>;
862 - * }
863 - * });
877 + * TODO: Delete this when all callers have been updated to rely on this
878 + * behavior being the default.
879 *
880 + * Backwards compatible stub for what is now the default behavior.
881 * @param {function} method Method to be bound.
882 * @public
883 */
884 autoBind: function(method) {
869 - function unbound() {
870 - invariant(
871 - false,
872 - 'React.autoBind(...): Attempted to invoke an auto-bound method that ' +
873 - 'was not correctly defined on the class specification.'
874 - );
875 - }
876 - unbound.__reactAutoBind = method;
877 - return unbound;
885 + return method;
886 }
879 -
887 };
888
889 module.exports = ReactCompositeComponent;
src/core/ReactDoNotBindDeprecated.js new
+48
@@ -0,0 +1,48 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDoNotBindDeprecated
17 + */
18 +
19 +var ReactDoNotBindDeprecated = {
20 + /**
21 + * Marks the method for not being automatically bound on component mounting. A
22 + * couple of reasons you might want to use this:
23 + *
24 + * - Automatically supporting the previous behavior in components that were
25 + * built with previous versions of React.
26 + * - Tuning performance, by avoiding binding on initial render for methods
27 + * that are always invoked while being preceded by `this.`. Such binds are
28 + * unnecessary.
29 + *
30 + * React.createClass({
31 + * handleClick: ReactDoNotBindDeprecated.doNotBind(function() {
32 + * alert(this.setState); // undefined!
33 + * }),
34 + * render: function() {
35 + * return <a onClick={this.handleClick}>Jump</a>;
36 + * }
37 + * });
38 + *
39 + * @param {function} method Method to avoid automatically binding.
40 + * @public
41 + */
42 + doNotBind: function(method) {
43 + method.__reactDontBind = true; // Mutating
44 + return method;
45 + }
46 +};
47 +
48 +module.exports = ReactDoNotBindDeprecated;
src/core/ReactID.js
+1 -1
@@ -30,7 +30,7 @@ var nodeCache = {};
30 * other objects so just return '' if we're given something other than a
31 * DOM node (such as window).
32 *
33 - * @param {DOMElement|DOMWindow|DOMDocument} node DOM node.
33 + * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
34 * @returns {string} ID of the supplied `domNode`.
35 */
36 function getID(node) {
src/core/__tests__/ReactBind-test.js
+13 -6
@@ -21,9 +21,11 @@
21
22 var mocks = require('mocks');
23 var React = require('React');
24 +var ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
25 var ReactTestUtils = require('ReactTestUtils');
26 var reactComponentExpect = require('reactComponentExpect');
27
28 +// TODO: Test render and all stock methods.
29 describe('React.autoBind', function() {
30
31 it('Holds reference to instance', function() {
@@ -31,16 +33,20 @@ describe('React.autoBind', function() {
33 var mouseDidEnter = mocks.getMockFunction();
34 var mouseDidLeave = mocks.getMockFunction();
35 var mouseDidClick = mocks.getMockFunction();
34 - var didBadIdea = mocks.getMockFunction();
36
37 var TestBindComponent = React.createClass({
37 - onMouseEnter: mouseDidEnter,
38 - onMouseLeave: mouseDidLeave,
38 + getInitialState: function() {
39 + return {something: 'hi'};
40 + },
41 + onMouseEnter: ReactDoNotBindDeprecated.doNotBind(mouseDidEnter),
42 + onMouseLeave: ReactDoNotBindDeprecated.doNotBind(mouseDidLeave),
43 onClick: React.autoBind(mouseDidClick),
44
41 - // autoBind needs to be on the top-level spec.
45 + // auto binding only occurs on top level functions in class defs.
46 badIdeas: {
43 - badBind: React.autoBind(didBadIdea)
47 + badBind: function() {
48 + this.state.something;
49 + }
50 },
51
52 render: function() {
@@ -48,7 +54,8 @@ describe('React.autoBind', function() {
54 <div
55 onMouseEnter={this.onMouseEnter.bind(this)}
56 onMouseLeave={this.onMouseLeave}
51 - onClick={this.onClick} />
57 + onClick={this.onClick}
58 + />
59 );
60 }
61 });
src/core/__tests__/ReactComponentLifeCycle-test.js
+8 -68
@@ -286,6 +286,8 @@ describe('ReactComponentLifeCycle', function() {
286 if (isInitialRender) {
287 this._testJournal.stateInInitialRender = clone(this.state);
288 this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
289 + this._testJournal.compositeLifeCycleInInitialRender =
290 + this._compositeLifeCycleState;
291 } else {
292 this._testJournal.stateInLaterRender = clone(this.state);
293 this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
@@ -319,7 +321,7 @@ describe('ReactComponentLifeCycle', function() {
321 GET_INIT_STATE_RETURN_VAL
322 );
323 expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)
322 - .toBe(ComponentLifeCycle.UNMOUNTED);
324 + .toBe(ComponentLifeCycle.MOUNTED);
325 expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)
326 .toBe(CompositeComponentLifeCycle.MOUNTING);
327
@@ -328,7 +330,7 @@ describe('ReactComponentLifeCycle', function() {
330 instance._testJournal.returnedFromGetInitialState
331 );
332 expect(instance._testJournal.lifeCycleAtStartOfWillMount)
331 - .toBe(ComponentLifeCycle.UNMOUNTED);
333 + .toBe(ComponentLifeCycle.MOUNTED);
334 expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)
335 .toBe(CompositeComponentLifeCycle.MOUNTING);
336
@@ -343,7 +345,10 @@ describe('ReactComponentLifeCycle', function() {
345 expect(instance._testJournal.stateInInitialRender)
346 .toEqual(INIT_RENDER_STATE);
347 expect(instance._testJournal.lifeCycleInInitialRender).toBe(
346 - ComponentLifeCycle.UNMOUNTED
348 + ComponentLifeCycle.MOUNTED
349 + );
350 + expect(instance._testJournal.compositeLifeCycleInInitialRender).toBe(
351 + CompositeComponentLifeCycle.MOUNTING
352 );
353
354 expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
@@ -429,70 +434,5 @@ describe('ReactComponentLifeCycle', function() {
434 expect(instance.state.stateField).toBe('goodbye');
435 });
436
432 - it('should call nested lifecycle methods in the right order', function() {
433 - var log;
434 - var logger = function(msg) {
435 - return function() {
436 - // return true for shouldComponentUpdate
437 - log.push(msg);
438 - return true;
439 - };
440 - };
441 - var Outer = React.createClass({
442 - render: function() {
443 - return <div><Inner x={this.props.x} /></div>;
444 - },
445 - componentWillMount: logger('outer componentWillMount'),
446 - componentDidMount: logger('outer componentDidMount'),
447 - componentWillReceiveProps: logger('outer componentWillReceiveProps'),
448 - shouldComponentUpdate: logger('outer shouldComponentUpdate'),
449 - componentWillUpdate: logger('outer componentWillUpdate'),
450 - componentDidUpdate: logger('outer componentDidUpdate'),
451 - componentWillUnmount: logger('outer componentWillUnmount')
452 - });
453 - var Inner = React.createClass({
454 - render: function() {
455 - return <span>{this.props.x}</span>;
456 - },
457 - componentWillMount: logger('inner componentWillMount'),
458 - componentDidMount: logger('inner componentDidMount'),
459 - componentWillReceiveProps: logger('inner componentWillReceiveProps'),
460 - shouldComponentUpdate: logger('inner shouldComponentUpdate'),
461 - componentWillUpdate: logger('inner componentWillUpdate'),
462 - componentDidUpdate: logger('inner componentDidUpdate'),
463 - componentWillUnmount: logger('inner componentWillUnmount')
464 - });
465 - var instance;
466 -
467 - log = [];
468 - instance = ReactTestUtils.renderIntoDocument(<Outer x={17} />);
469 - expect(log).toEqual([
470 - 'outer componentWillMount',
471 - 'inner componentWillMount',
472 - 'inner componentDidMount',
473 - 'outer componentDidMount'
474 - ]);
475 -
476 - log = [];
477 - instance.setProps({x: 42});
478 - expect(log).toEqual([
479 - 'outer componentWillReceiveProps',
480 - 'outer shouldComponentUpdate',
481 - 'outer componentWillUpdate',
482 - 'inner componentWillReceiveProps',
483 - 'inner shouldComponentUpdate',
484 - 'inner componentWillUpdate',
485 - 'inner componentDidUpdate',
486 - 'outer componentDidUpdate'
487 - ]);
488 -
489 - log = [];
490 - instance.unmountComponent();
491 - expect(log).toEqual([
492 - 'outer componentWillUnmount',
493 - 'inner componentWillUnmount'
494 - ]);
495 - });
496 -
437 });
438
src/core/__tests__/ReactCompositeComponent-test.js
+35 -15
@@ -27,6 +27,7 @@ var ReactCurrentOwner;
27 var ReactProps;
28 var ReactTestUtils;
29 var ReactID;
30 +var ReactDoNotBindDeprecated;
31
32 var cx;
33 var reactComponentExpect;
@@ -38,6 +39,7 @@ describe('ReactCompositeComponent', function() {
39 reactComponentExpect = require('reactComponentExpect');
40 React = require('React');
41 ReactCurrentOwner = require('ReactCurrentOwner');
42 + ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
43 ReactProps = require('ReactProps');
44 ReactTestUtils = require('ReactTestUtils');
45 ReactID = require('ReactID');
@@ -52,7 +54,7 @@ describe('ReactCompositeComponent', function() {
54 },
55
56 render: function() {
55 - var toggleActivatedState = this._toggleActivatedState.bind(this);
57 + var toggleActivatedState = this._toggleActivatedState;
58 return !this.state.activated ?
59 <a ref="x" onClick={toggleActivatedState} /> :
60 <b ref="x" onClick={toggleActivatedState} />;
@@ -64,9 +66,9 @@ describe('ReactCompositeComponent', function() {
66 return {activated: false};
67 },
68
67 - _toggleActivatedState: React.autoBind(function() {
69 + _toggleActivatedState:function() {
70 this.setState({activated: !this.state.activated});
69 - }),
71 + },
72
73 render: function() {
74 return !this.state.activated ?
@@ -167,15 +169,18 @@ describe('ReactCompositeComponent', function() {
169 });
170
171 it('should auto bind methods and values correctly', function() {
170 - var RETURN_VALUE_AFTER_MOUNT = 'returnValue';
172 var ComponentClass = React.createClass({
173 getInitialState: function() {
173 - return {
174 - valueToReturn: RETURN_VALUE_AFTER_MOUNT
175 - };
174 + return {valueToReturn: 'hi'};
175 + },
176 + methodToBeExplicitlyBound: function() {
177 + return this;
178 },
177 - methodBoundOnMount: React.autoBind(function() {
178 - return this.state.valueToReturn;
179 + methodAutoBound: function() {
180 + return this;
181 + },
182 + methodExplicitlyNotBound: ReactDoNotBindDeprecated.doNotBind(function() {
183 + return this;
184 }),
185 render: function() {
186 return <div> </div>;
@@ -184,18 +189,33 @@ describe('ReactCompositeComponent', function() {
189 var instance = <ComponentClass />;
190
191 // Autobound methods will throw before mounting.
192 + // TODO: We should actually allow component instance methods to be invoked
193 + // before mounting for read-only operations. We would then update this test.
194 + expect(function() {
195 + instance.methodToBeExplicitlyBound.bind(instance)();
196 + }).toThrow();
197 expect(function() {
188 - instance.methodBoundOnMount();
198 + instance.methodAutoBound();
199 }).toThrow();
200 + expect(function() {
201 + instance.methodExplicitlyNotBound();
202 + }).not.toThrow();
203
204 // Next, prove that once mounted, the scope is bound correctly to the actual
205 // component.
206 ReactTestUtils.renderIntoDocument(instance);
194 - var retValAfterMount = instance.methodBoundOnMount();
195 - expect(retValAfterMount).toBe(RETURN_VALUE_AFTER_MOUNT);
196 - var retValAfterMountWithCrazyScope =
197 - instance.methodBoundOnMount.call({thisIsACrazyScope:null});
198 - expect(retValAfterMountWithCrazyScope).toBe(RETURN_VALUE_AFTER_MOUNT);
207 + var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
208 + var autoBound = instance.methodAutoBound;
209 + var explicitlyNotBound = instance.methodExplicitlyNotBound;
210 +
211 + expect(explicitlyBound.call(null)).toBe(instance);
212 + expect(autoBound.call(null)).toBe(instance);
213 + expect(explicitlyNotBound.call(null)).toBe(null);
214 +
215 + expect(explicitlyBound.call(instance)).toBe(instance);
216 + expect(autoBound.call(instance)).toBe(instance);
217 + expect(explicitlyNotBound.call(instance)).toBe(instance);
218 +
219 });
220
221 it('should normalize props with default values', function() {