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() {