Skip to content

Commit

Permalink
ReactNative's ref.measureLayout now takes a ref (#15126)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elicwhite authored Mar 29, 2019
1 parent 1b94fd2 commit 2e02469
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 4 deletions.
27 changes: 25 additions & 2 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,36 @@ class ReactFabricHostComponent {
}

measureLayout(
relativeToNativeNode: number,
relativeToNativeNode: number | Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) {
let relativeNode;

if (typeof relativeToNativeNode === 'number') {
// Already a node handle
relativeNode = relativeToNativeNode;
} else if (relativeToNativeNode._nativeTag) {
relativeNode = relativeToNativeNode._nativeTag;
} else if (
relativeToNativeNode.canonical &&
relativeToNativeNode.canonical._nativeTag
) {
relativeNode = relativeToNativeNode.canonical._nativeTag;
}

if (relativeNode == null) {
warningWithoutStack(
false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
);

return;
}

UIManager.measureLayout(
this._nativeTag,
relativeToNativeNode,
relativeNode,
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,36 @@ class ReactNativeFiberHostComponent {
}

measureLayout(
relativeToNativeNode: number,
relativeToNativeNode: number | Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) {
let relativeNode;

if (typeof relativeToNativeNode === 'number') {
// Already a node handle
relativeNode = relativeToNativeNode;
} else if (relativeToNativeNode._nativeTag) {
relativeNode = relativeToNativeNode._nativeTag;
} else if (
relativeToNativeNode.canonical &&
relativeToNativeNode.canonical._nativeTag
) {
relativeNode = relativeToNativeNode.canonical._nativeTag;
}

if (relativeNode == null) {
warningWithoutStack(
false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
);

return;
}

UIManager.measureLayout(
this._nativeTag,
relativeToNativeNode,
relativeNode,
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
);
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-renderer/src/__mocks__/UIManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const RCTUIManager = {
views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
}),
replaceExistingNonRootView: jest.fn(),
measureLayout: jest.fn(),
__takeSnapshot: jest.fn(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,61 @@ describe('ReactFabric', () => {
});
});

it('should support ref in ref.measureLayout', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'RCTView',
}));

[View].forEach(Component => {
UIManager.measureLayout.mockReset();

let viewRef;
let otherRef;
ReactFabric.render(
<Component>
<Component
foo="bar"
ref={ref => {
viewRef = ref;
}}
/>
<View
ref={ref => {
otherRef = ref;
}}
/>
</Component>,
11,
);

expect(UIManager.measureLayout).not.toBeCalled();

const successCallback = jest.fn();
const failureCallback = jest.fn();
viewRef.measureLayout(otherRef, successCallback, failureCallback);

expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
expect(UIManager.measureLayout).toHaveBeenCalledWith(
expect.any(Number),
expect.any(Number),
expect.any(Function),
expect.any(Function),
);

const args = UIManager.measureLayout.mock.calls[0];
expect(args[0]).not.toEqual(args[1]);
expect(successCallback).not.toBeCalled();
expect(failureCallback).not.toBeCalled();
args[2]('fail');
expect(failureCallback).toBeCalledWith('fail');

expect(successCallback).not.toBeCalled();
args[3]('success');
expect(successCallback).toBeCalledWith('success');
});
});

it('returns the correct instance and calls it in the callback', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,133 @@ describe('ReactNative', () => {
});
});

it('should support reactTag in ref.measureLayout', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'RCTView',
}));

class Subclass extends ReactNative.NativeComponent {
render() {
return <View>{this.props.children}</View>;
}
}

const CreateClass = createReactClass({
mixins: [NativeMethodsMixin],
render() {
return <View>{this.props.children}</View>;
},
});

[View, Subclass, CreateClass].forEach(Component => {
UIManager.measureLayout.mockReset();

let viewRef;
let otherRef;
ReactNative.render(
<Component>
<Component
foo="bar"
ref={ref => {
viewRef = ref;
}}
/>
<Component
ref={ref => {
otherRef = ref;
}}
/>
</Component>,
11,
);

expect(UIManager.measureLayout).not.toBeCalled();

const successCallback = jest.fn();
const failureCallback = jest.fn();
viewRef.measureLayout(
ReactNative.findNodeHandle(otherRef),
successCallback,
failureCallback,
);

expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
expect(UIManager.measureLayout).toHaveBeenCalledWith(
expect.any(Number),
expect.any(Number),
expect.any(Function),
expect.any(Function),
);

const args = UIManager.measureLayout.mock.calls[0];
expect(args[0]).not.toEqual(args[1]);
expect(successCallback).not.toBeCalled();
expect(failureCallback).not.toBeCalled();
args[2]('fail');
expect(failureCallback).toBeCalledWith('fail');

expect(successCallback).not.toBeCalled();
args[3]('success');
expect(successCallback).toBeCalledWith('success');
});
});

it('should support ref in ref.measureLayout of host components', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'RCTView',
}));

[View].forEach(Component => {
UIManager.measureLayout.mockReset();

let viewRef;
let otherRef;
ReactNative.render(
<Component>
<Component
foo="bar"
ref={ref => {
viewRef = ref;
}}
/>
<View
ref={ref => {
otherRef = ref;
}}
/>
</Component>,
11,
);

expect(UIManager.measureLayout).not.toBeCalled();

const successCallback = jest.fn();
const failureCallback = jest.fn();
viewRef.measureLayout(otherRef, successCallback, failureCallback);

expect(UIManager.measureLayout).toHaveBeenCalledTimes(1);
expect(UIManager.measureLayout).toHaveBeenCalledWith(
expect.any(Number),
expect.any(Number),
expect.any(Function),
expect.any(Function),
);

const args = UIManager.measureLayout.mock.calls[0];
expect(args[0]).not.toEqual(args[1]);
expect(successCallback).not.toBeCalled();
expect(failureCallback).not.toBeCalled();
args[2]('fail');
expect(failureCallback).toBeCalledWith('fail');

expect(successCallback).not.toBeCalled();
args[3]('success');
expect(successCallback).toBeCalledWith('success');
});
});

it('returns the correct instance and calls it in the callback', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
Expand Down

0 comments on commit 2e02469

Please sign in to comment.