@samitouri / QOS-React / commits / 2e02469fa2

ReactNative's ref.measureLayout now takes a ref (#15126)

* ReactNative's ref.measureLayout now takes a ref * Use Object as the additional param type * Remove unnecessary whitespace * Not supporting ref in mixin or subclass

Eli White committed Mar 29, 2019 at 15:57 UTC 2e02469fa22d54b0a695037b82843eeaab56b364
5 files changed +233 -4
packages/react-native-renderer/src/ReactFabricHostConfig.js
+25 -2
@@ -119,13 +119,36 @@ class ReactFabricHostComponent {
119 }
120
121 measureLayout(
122 - relativeToNativeNode: number,
122 + relativeToNativeNode: number | Object,
123 onSuccess: MeasureLayoutOnSuccessCallback,
124 onFail: () => void /* currently unused */,
125 ) {
126 + let relativeNode;
127 +
128 + if (typeof relativeToNativeNode === 'number') {
129 + // Already a node handle
130 + relativeNode = relativeToNativeNode;
131 + } else if (relativeToNativeNode._nativeTag) {
132 + relativeNode = relativeToNativeNode._nativeTag;
133 + } else if (
134 + relativeToNativeNode.canonical &&
135 + relativeToNativeNode.canonical._nativeTag
136 + ) {
137 + relativeNode = relativeToNativeNode.canonical._nativeTag;
138 + }
139 +
140 + if (relativeNode == null) {
141 + warningWithoutStack(
142 + false,
143 + 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
144 + );
145 +
146 + return;
147 + }
148 +
149 UIManager.measureLayout(
150 this._nativeTag,
128 - relativeToNativeNode,
151 + relativeNode,
152 mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
153 mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
154 );
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+25 -2
@@ -70,13 +70,36 @@ class ReactNativeFiberHostComponent {
70 }
71
72 measureLayout(
73 - relativeToNativeNode: number,
73 + relativeToNativeNode: number | Object,
74 onSuccess: MeasureLayoutOnSuccessCallback,
75 onFail: () => void /* currently unused */,
76 ) {
77 + let relativeNode;
78 +
79 + if (typeof relativeToNativeNode === 'number') {
80 + // Already a node handle
81 + relativeNode = relativeToNativeNode;
82 + } else if (relativeToNativeNode._nativeTag) {
83 + relativeNode = relativeToNativeNode._nativeTag;
84 + } else if (
85 + relativeToNativeNode.canonical &&
86 + relativeToNativeNode.canonical._nativeTag
87 + ) {
88 + relativeNode = relativeToNativeNode.canonical._nativeTag;
89 + }
90 +
91 + if (relativeNode == null) {
92 + warningWithoutStack(
93 + false,
94 + 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
95 + );
96 +
97 + return;
98 + }
99 +
100 UIManager.measureLayout(
101 this._nativeTag,
79 - relativeToNativeNode,
102 + relativeNode,
103 mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
104 mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
105 );
packages/react-native-renderer/src/__mocks__/UIManager.js
+1
@@ -153,6 +153,7 @@ const RCTUIManager = {
153 views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
154 }),
155 replaceExistingNonRootView: jest.fn(),
156 + measureLayout: jest.fn(),
157 __takeSnapshot: jest.fn(),
158 };
159
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+55
@@ -302,6 +302,61 @@ describe('ReactFabric', () => {
302 });
303 });
304
305 + it('should support ref in ref.measureLayout', () => {
306 + const View = createReactNativeComponentClass('RCTView', () => ({
307 + validAttributes: {foo: true},
308 + uiViewClassName: 'RCTView',
309 + }));
310 +
311 + [View].forEach(Component => {
312 + UIManager.measureLayout.mockReset();
313 +
314 + let viewRef;
315 + let otherRef;
316 + ReactFabric.render(
317 + <Component>
318 + <Component
319 + foo="bar"
320 + ref={ref => {
321 + viewRef = ref;
322 + }}
323 + />
324 + <View
325 + ref={ref => {
326 + otherRef = ref;
327 + }}
328 + />
329 + </Component>,
330 + 11,
331 + );
332 +
333 + expect(UIManager.measureLayout).not.toBeCalled();
334 +
335 + const successCallback = jest.fn();
336 + const failureCallback = jest.fn();
337 + viewRef.measureLayout(otherRef, successCallback, failureCallback);
338 +
339 + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
340 + expect(UIManager.measureLayout).toHaveBeenCalledWith(
341 + expect.any(Number),
342 + expect.any(Number),
343 + expect.any(Function),
344 + expect.any(Function),
345 + );
346 +
347 + const args = UIManager.measureLayout.mock.calls[0];
348 + expect(args[0]).not.toEqual(args[1]);
349 + expect(successCallback).not.toBeCalled();
350 + expect(failureCallback).not.toBeCalled();
351 + args[2]('fail');
352 + expect(failureCallback).toBeCalledWith('fail');
353 +
354 + expect(successCallback).not.toBeCalled();
355 + args[3]('success');
356 + expect(successCallback).toBeCalledWith('success');
357 + });
358 + });
359 +
360 it('returns the correct instance and calls it in the callback', () => {
361 const View = createReactNativeComponentClass('RCTView', () => ({
362 validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+127
@@ -249,6 +249,133 @@ describe('ReactNative', () => {
249 });
250 });
251
252 + it('should support reactTag in ref.measureLayout', () => {
253 + const View = createReactNativeComponentClass('RCTView', () => ({
254 + validAttributes: {foo: true},
255 + uiViewClassName: 'RCTView',
256 + }));
257 +
258 + class Subclass extends ReactNative.NativeComponent {
259 + render() {
260 + return <View>{this.props.children}</View>;
261 + }
262 + }
263 +
264 + const CreateClass = createReactClass({
265 + mixins: [NativeMethodsMixin],
266 + render() {
267 + return <View>{this.props.children}</View>;
268 + },
269 + });
270 +
271 + [View, Subclass, CreateClass].forEach(Component => {
272 + UIManager.measureLayout.mockReset();
273 +
274 + let viewRef;
275 + let otherRef;
276 + ReactNative.render(
277 + <Component>
278 + <Component
279 + foo="bar"
280 + ref={ref => {
281 + viewRef = ref;
282 + }}
283 + />
284 + <Component
285 + ref={ref => {
286 + otherRef = ref;
287 + }}
288 + />
289 + </Component>,
290 + 11,
291 + );
292 +
293 + expect(UIManager.measureLayout).not.toBeCalled();
294 +
295 + const successCallback = jest.fn();
296 + const failureCallback = jest.fn();
297 + viewRef.measureLayout(
298 + ReactNative.findNodeHandle(otherRef),
299 + successCallback,
300 + failureCallback,
301 + );
302 +
303 + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
304 + expect(UIManager.measureLayout).toHaveBeenCalledWith(
305 + expect.any(Number),
306 + expect.any(Number),
307 + expect.any(Function),
308 + expect.any(Function),
309 + );
310 +
311 + const args = UIManager.measureLayout.mock.calls[0];
312 + expect(args[0]).not.toEqual(args[1]);
313 + expect(successCallback).not.toBeCalled();
314 + expect(failureCallback).not.toBeCalled();
315 + args[2]('fail');
316 + expect(failureCallback).toBeCalledWith('fail');
317 +
318 + expect(successCallback).not.toBeCalled();
319 + args[3]('success');
320 + expect(successCallback).toBeCalledWith('success');
321 + });
322 + });
323 +
324 + it('should support ref in ref.measureLayout of host components', () => {
325 + const View = createReactNativeComponentClass('RCTView', () => ({
326 + validAttributes: {foo: true},
327 + uiViewClassName: 'RCTView',
328 + }));
329 +
330 + [View].forEach(Component => {
331 + UIManager.measureLayout.mockReset();
332 +
333 + let viewRef;
334 + let otherRef;
335 + ReactNative.render(
336 + <Component>
337 + <Component
338 + foo="bar"
339 + ref={ref => {
340 + viewRef = ref;
341 + }}
342 + />
343 + <View
344 + ref={ref => {
345 + otherRef = ref;
346 + }}
347 + />
348 + </Component>,
349 + 11,
350 + );
351 +
352 + expect(UIManager.measureLayout).not.toBeCalled();
353 +
354 + const successCallback = jest.fn();
355 + const failureCallback = jest.fn();
356 + viewRef.measureLayout(otherRef, successCallback, failureCallback);
357 +
358 + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
359 + expect(UIManager.measureLayout).toHaveBeenCalledWith(
360 + expect.any(Number),
361 + expect.any(Number),
362 + expect.any(Function),
363 + expect.any(Function),
364 + );
365 +
366 + const args = UIManager.measureLayout.mock.calls[0];
367 + expect(args[0]).not.toEqual(args[1]);
368 + expect(successCallback).not.toBeCalled();
369 + expect(failureCallback).not.toBeCalled();
370 + args[2]('fail');
371 + expect(failureCallback).toBeCalledWith('fail');
372 +
373 + expect(successCallback).not.toBeCalled();
374 + args[3]('success');
375 + expect(successCallback).toBeCalledWith('success');
376 + });
377 + });
378 +
379 it('returns the correct instance and calls it in the callback', () => {
380 const View = createReactNativeComponentClass('RCTView', () => ({
381 validAttributes: {foo: true},