Skip to content

Commit 44bfa43

Browse files
committed
Merge remote-tracking branch 'upstream/rn-upgrade/0.81.5-no-unit-tests' into rn-upgrade/0.81.5-no-unit-tests
2 parents f777756 + f5ea751 commit 44bfa43

10 files changed

Lines changed: 97 additions & 200 deletions

File tree

app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.test.tsx

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Third party dependencies
22
import React, { useRef, useEffect } from 'react';
3-
import { render, waitFor, cleanup } from '@testing-library/react-native';
3+
import { render, act, waitFor } from '@testing-library/react-native';
44

55
// External dependencies.
66
import Text from '../../../../Texts/Text';
@@ -32,18 +32,6 @@ jest.mock('@react-navigation/native', () => {
3232
});
3333

3434
describe('BottomSheetDialog', () => {
35-
beforeEach(() => {
36-
jest.clearAllMocks();
37-
// Ensure Platform.OS is always reset to 'ios' before each test,
38-
// in case a previous test (in a batched run) changed it.
39-
Platform.OS = 'ios';
40-
});
41-
42-
afterEach(() => {
43-
cleanup();
44-
jest.restoreAllMocks();
45-
});
46-
4735
it('should render correctly', () => {
4836
const wrapper = render(<BottomSheetDialog />);
4937
expect(wrapper.toJSON()).toBeDefined();
@@ -82,6 +70,8 @@ describe('BottomSheetDialog', () => {
8270
});
8371

8472
it('should call onClose when onCloseDialog ref is called', async () => {
73+
Platform.OS = 'ios';
74+
8575
const onCloseMock = jest.fn();
8676
const TestComponent = () => {
8777
const ref = useRef<BottomSheetDialogRef>(null);
@@ -105,7 +95,7 @@ describe('BottomSheetDialog', () => {
10595
expect(onCloseMock).toHaveBeenCalled();
10696
});
10797
});
108-
it('calls onClose each time when onCloseDialog is invoked twice', async () => {
98+
it('calls onClose only once when onCloseDialog is invoked twice rapidly', async () => {
10999
const onCloseMock = jest.fn();
110100
const TestComponent = () => {
111101
const ref = useRef<BottomSheetDialogRef>(null);
@@ -124,11 +114,62 @@ describe('BottomSheetDialog', () => {
124114
);
125115
};
126116

117+
render(<TestComponent />);
118+
await waitFor(() => {
119+
expect(onCloseMock).toHaveBeenCalledTimes(1);
120+
});
121+
});
122+
123+
it('allows closing again after re-opening', async () => {
124+
const onCloseMock = jest.fn();
125+
const onOpenMock = jest.fn();
126+
const TestComponent = () => {
127+
const ref = useRef<BottomSheetDialogRef>(null);
128+
129+
useEffect(() => {
130+
if (ref.current) {
131+
ref.current?.onCloseDialog();
132+
ref.current?.onOpenDialog();
133+
ref.current?.onCloseDialog();
134+
}
135+
}, []);
136+
137+
return (
138+
<BottomSheetDialog ref={ref} onClose={onCloseMock} onOpen={onOpenMock}>
139+
<Text>Test Child</Text>
140+
</BottomSheetDialog>
141+
);
142+
};
143+
127144
render(<TestComponent />);
128145
await waitFor(() => {
129146
expect(onCloseMock).toHaveBeenCalledTimes(2);
130147
});
131148
});
149+
it('calls onClose only once when onCloseDialog is invoked twice rapidly', async () => {
150+
const onCloseMock = jest.fn();
151+
const TestComponent = () => {
152+
const ref = useRef<BottomSheetDialogRef>(null);
153+
154+
useEffect(() => {
155+
if (ref.current) {
156+
ref.current?.onCloseDialog();
157+
ref.current?.onCloseDialog();
158+
}
159+
}, []);
160+
161+
return (
162+
<BottomSheetDialog ref={ref} onClose={onCloseMock}>
163+
<Text>Test Child</Text>
164+
</BottomSheetDialog>
165+
);
166+
};
167+
168+
render(<TestComponent />);
169+
await waitFor(() => {
170+
expect(onCloseMock).toHaveBeenCalledTimes(1);
171+
});
172+
});
132173

133174
it('allows closing again after re-opening', async () => {
134175
const onCloseMock = jest.fn();

app/component-library/components/BottomSheets/BottomSheet/foundation/BottomSheetDialog/BottomSheetDialog.tsx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ import {
3030
useSafeAreaFrame,
3131
useSafeAreaInsets,
3232
} from 'react-native-safe-area-context';
33-
import { debounce } from 'lodash';
34-
3533
// External dependencies.
3634
import { useStyles } from '../../../../../hooks';
3735

@@ -88,6 +86,7 @@ const BottomSheetDialog = forwardRef<
8886
const topOfDialogYValue = useSharedValue(0);
8987
const bottomOfDialogYValue = useSharedValue(screenHeight);
9088
const isMounted = useRef(false);
89+
const isClosingRef = useRef(false);
9190

9291
const onOpenCB = useCallback(() => {
9392
onOpen?.();
@@ -97,6 +96,8 @@ const BottomSheetDialog = forwardRef<
9796
}, [onClose]);
9897

9998
const onCloseDialog = useCallback(() => {
99+
if (isClosingRef.current) return;
100+
isClosingRef.current = true;
100101
currentYOffset.value = withTiming(
101102
bottomOfDialogYValue.value,
102103
{ duration: DEFAULT_BOTTOMSHEETDIALOG_DISPLAY_DURATION },
@@ -177,6 +178,7 @@ const BottomSheetDialog = forwardRef<
177178

178179
// Animate in sheet on initial render.
179180
const onOpenDialog = () => {
181+
isClosingRef.current = false;
180182
// Starts setting the Y position of the dialog to the bottom of the dialog
181183
currentYOffset.value = bottomOfDialogYValue.value;
182184
// Animate the Y position to the top of the dialog, then call onOpenCB
@@ -189,21 +191,6 @@ const BottomSheetDialog = forwardRef<
189191
);
190192
};
191193

192-
const onDebouncedCloseDialog = useMemo(
193-
// Prevent hide from being called multiple times. Potentially caused by taps in quick succession.
194-
() => debounce(onCloseDialog, 2000, { leading: true }),
195-
[onCloseDialog],
196-
);
197-
198-
useEffect(
199-
() =>
200-
// Automatically handles animation when content changes
201-
// Disable for now since network switches causes the screen to hang with this on.
202-
// LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
203-
onDebouncedCloseDialog.cancel(),
204-
[children, onDebouncedCloseDialog],
205-
);
206-
207194
const updateSheetHeight = (e: LayoutChangeEvent) => {
208195
const { height } = e.nativeEvent.layout;
209196
bottomOfDialogYValue.value = height;

app/components/Views/ManualBackupStep3/index.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ const HARDWARE_BACK_PRESS = 'hardwareBackPress';
2727
class ManualBackupStep3 extends PureComponent {
2828
backHandlerSubscription = null;
2929

30-
constructor(props) {
31-
super(props);
32-
this.steps = props.route.params?.steps;
33-
}
34-
3530
state = {
3631
showHint: false,
3732
hintText: '',

app/core/BackgroundBridge/createDupeReqFilterStream.ts

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,48 +48,32 @@ const makeExpirySet = () => {
4848
};
4949
};
5050

51-
/**
52-
* JSON-RPC request deduper. Extends Transform and overrides `_destroy` so we
53-
* do not pass `options.destroy` into the constructor: readable-stream assigns
54-
* that to `this._destroy`, which replaces Transform's default teardown and
55-
* causes pump/end-of-stream to report "premature close" when the bridge tears
56-
* down.
57-
*/
58-
class DupeReqFilterTransform extends Transform {
59-
private readonly seenRequestIds = makeExpirySet();
60-
61-
constructor() {
62-
super({ objectMode: true });
63-
}
64-
65-
_transform(
66-
chunk: JsonRpcRequest,
67-
_encoding: BufferEncoding,
68-
cb: (error?: Error | null, data?: JsonRpcRequest) => void,
69-
): void {
70-
// JSON-RPC notifications have no ids; our only recourse is to let them through.
71-
const hasNoId = chunk.id === undefined;
72-
const requestNotYetSeen = this.seenRequestIds.add(chunk.id);
73-
74-
if (hasNoId || requestNotYetSeen) {
75-
cb(null, chunk);
76-
} else {
77-
cb();
78-
}
79-
}
80-
81-
_destroy(error: Error | null, callback: (error: Error | null) => void): void {
82-
this.seenRequestIds.destroy();
83-
super._destroy(error, callback);
84-
}
85-
}
86-
8751
/**
8852
* Returns a transform stream that filters out requests whose ids we've already seen.
8953
* Ignores JSON-RPC notifications, i.e. requests with an `undefined` id.
9054
*
9155
* @returns The stream object.
9256
*/
93-
export default function createDupeReqFilterStream(): DupeReqFilterTransform {
94-
return new DupeReqFilterTransform();
57+
export default function createDupeReqFilterStream() {
58+
const seenRequestIds = makeExpirySet();
59+
return new Transform({
60+
transform(chunk: JsonRpcRequest, _, cb) {
61+
// JSON-RPC notifications have no ids; our only recourse is to let them through.
62+
const hasNoId = chunk.id === undefined;
63+
const requestNotYetSeen = seenRequestIds.add(chunk.id);
64+
65+
if (hasNoId || requestNotYetSeen) {
66+
cb(null, chunk);
67+
} else {
68+
// eslint-disable-next-line no-console
69+
console.debug(`RPC request with id "${chunk.id}" already seen.`);
70+
cb();
71+
}
72+
},
73+
destroy(error, cb) {
74+
seenRequestIds.destroy();
75+
cb(error);
76+
},
77+
objectMode: true,
78+
});
9579
}

app/core/Engine/controllers/snaps/execution-service-init.ts

Lines changed: 2 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -5,96 +5,13 @@ import {
55
// eslint-disable-next-line import-x/no-nodejs-modules
66
import { Duplex } from 'stream';
77
import { MessengerClientInitFunction } from '../../types';
8+
import { WebViewExecutionService } from '@metamask/snaps-controllers/react-native';
89
import { createWebView, removeWebView } from '../../../../lib/snaps';
910
import Logger from '../../../../util/Logger';
1011
import { SnapBridge } from '../../../Snaps';
1112
import getRpcMethodMiddleware from '../../../RPCMethods/RPCMethodMiddleware';
1213
import { SnapId } from '@metamask/snaps-sdk';
1314

14-
type WebViewExecutionServiceCtor = new (args: {
15-
messenger: ExecutionServiceMessenger;
16-
setupSnapProvider: (snapId: string, connectionStream: Duplex) => void;
17-
createWebView: typeof createWebView;
18-
removeWebView: typeof removeWebView;
19-
}) => ExecutionService<unknown>;
20-
21-
const ensureMessageEventShim = () => {
22-
const globalScope = globalThis as typeof globalThis & {
23-
MessageEvent?: new (
24-
type: string,
25-
init?: { data?: unknown; origin?: string; source?: unknown },
26-
) => {
27-
type: string;
28-
data?: unknown;
29-
origin?: string;
30-
source?: unknown;
31-
};
32-
};
33-
34-
if (typeof globalScope.MessageEvent === 'function') {
35-
return;
36-
}
37-
38-
class RNMessageEvent {
39-
type: string;
40-
data?: unknown;
41-
private _origin?: string;
42-
private _source?: unknown;
43-
44-
constructor(
45-
type: string,
46-
init?: { data?: unknown; origin?: string; source?: unknown },
47-
) {
48-
this.type = type;
49-
this.data = init?.data;
50-
this._origin = init?.origin;
51-
this._source = init?.source;
52-
}
53-
}
54-
55-
Object.defineProperty(RNMessageEvent.prototype, 'origin', {
56-
configurable: true,
57-
enumerable: false,
58-
get() {
59-
return this._origin;
60-
},
61-
set(value: unknown) {
62-
this._origin = value as string | undefined;
63-
},
64-
});
65-
66-
Object.defineProperty(RNMessageEvent.prototype, 'source', {
67-
configurable: true,
68-
enumerable: false,
69-
get() {
70-
return this._source;
71-
},
72-
set(value: unknown) {
73-
this._source = value;
74-
},
75-
});
76-
77-
globalScope.MessageEvent = RNMessageEvent as typeof globalScope.MessageEvent;
78-
};
79-
80-
const getWebViewExecutionServiceCtor = (): WebViewExecutionServiceCtor => {
81-
ensureMessageEventShim();
82-
83-
const snapsControllersRN =
84-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
85-
require('@metamask/snaps-controllers/react-native') as {
86-
WebViewExecutionService?: WebViewExecutionServiceCtor;
87-
};
88-
89-
if (!snapsControllersRN.WebViewExecutionService) {
90-
throw new Error(
91-
'Snaps react-native WebViewExecutionService is unavailable after module import.',
92-
);
93-
}
94-
95-
return snapsControllersRN.WebViewExecutionService;
96-
};
97-
9815
/**
9916
* Initialize the Snaps execution service.
10017
*
@@ -106,8 +23,6 @@ export const executionServiceInit: MessengerClientInitFunction<
10623
ExecutionService,
10724
ExecutionServiceMessenger
10825
> = ({ controllerMessenger }) => {
109-
const WebViewExecutionService = getWebViewExecutionServiceCtor();
110-
11126
/**
11227
* Set up the EIP-1193 provider for the given Snap.
11328
*
@@ -148,6 +63,7 @@ export const executionServiceInit: MessengerClientInitFunction<
14863
return {
14964
controller: new WebViewExecutionService({
15065
messenger: controllerMessenger,
66+
// @ts-expect-error The stream type doesn't match because of a version mismatch.
15167
setupSnapProvider,
15268
createWebView,
15369
removeWebView,

app/core/ErrorHandler/ErrorHandler.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ export const setReactNativeDefaultHandler = (handler: ErrorHandlerCallback) => {
1111
};
1212

1313
export const handleCustomError = (error: Error, isFatal: boolean) => {
14-
// Suppress stream "Premature close" errors that bubble up as uncaught
15-
// exceptions when Snap WebView execution environments tear down.
16-
// These are benign teardown artifacts caused by readable-stream version
17-
// mismatches between @metamask packages and the pump/end-of-stream libs.
18-
if (
19-
error.message === 'Premature close' ||
20-
(error as NodeJS.ErrnoException).code === 'ERR_STREAM_PREMATURE_CLOSE'
21-
) {
22-
return;
23-
}
2414
// Check whether the error is from the Ledger native bluetooth errors.
2515
if (
2616
error.name === 'EthAppPleaseEnableContractData' ||

app/core/MobilePortStream.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,6 @@ export default class PortDuplexStream extends Duplex {
4040
* @private
4141
*/
4242
_onDisconnect = function () {
43-
// Push null to signal readable end-of-stream, then end the writable side.
44-
// This sets _readableState.ended and _writableState.ended before the
45-
// 'close' event fires, preventing ERR_STREAM_PREMATURE_CLOSE from the
46-
// end-of-stream listener used by pump().
47-
if (!this._readableState?.ended) {
48-
this.push(null);
49-
}
50-
if (!this._writableState?.ended) {
51-
this.end();
52-
}
5343
this.destroy && this.destroy();
5444
};
5545

0 commit comments

Comments
 (0)