Skip to content

Commit 57c073f

Browse files
chiciometa-codesync[bot]
authored andcommitted
Fix AccessibilityInfo.prefersCrossFadeTransitions unresolved promise (facebook#55920)
Summary: This PR fixes a bug I found while exploring the codebase in the `Components` folder. `AccessibilityInfo` exposes the method `prefersCrossFadeTransitions`. In the android branch, `Promise.resolve(false)` as return value was creating a new unrelated promise that never calls the resolve function provided by the promise executor constructor, so it was hangs indefinitely. This means that if a user calls this method on android without any `Platform.OS === ios` guard, it will result in a unresolved promise. I fixed the bug by aligning the implementation of `prefersCrossFadeTransitions` to the one of other methods (eg. `isBoldTextEnabled`). I also added the tests to avoid regression, and additionally verify that the iOS method is doing what we are expecting (in both cases for when `NativeAccessibilityManagerIOS.getCurrentPrefersCrossFadeTransitionsState` is available or not). The mock of the `Platform` object has been done the same way I saw while doing another contribution in the `Pressability-test` (see facebook#55378 ). I noticed the same bug in `isHighTextContrastEnabled` and `isDarkerSystemColorsEnabled` and I plan/would like to address them in follow-up PRs (and adding all the missing tests). ## Changelog: [GENERAL] [FIXED] - Fix AccessibilityInfo.prefersCrossFadeTransitions unresolved (never ending) promise Pull Request resolved: facebook#55920 Test Plan: This fix has been develop in TDD. I first added a failing test to reproduce that the android branch of the `prefersCrossFadeTransitions` method was acting as described above (resulting in failure due to jest timeout because the promise was not returning). Then I applied the fix, and finally added also the test for the iOS counterpart. One final note: `AccessibilityInfo` is mocked globally in `setup.js` (via `setupFiles` in the Jest preset). So to test the real implementation I had to use `jest.unmock` (pattern that I saw is already used in other component test files like View-test.js and TextInput-test.js). Reviewed By: vzaidman Differential Revision: D95565605 Pulled By: cipolleschi fbshipit-source-id: c666d198de593a79324801c181d571807d874bd5
1 parent 3057e28 commit 57c073f

2 files changed

Lines changed: 106 additions & 6 deletions

File tree

packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ const AccessibilityInfo = {
269269
* See https://reactnative.dev/docs/accessibilityinfo#prefersCrossFadeTransitions
270270
*/
271271
prefersCrossFadeTransitions(): Promise<boolean> {
272-
return new Promise((resolve, reject) => {
273-
if (Platform.OS === 'android') {
274-
return Promise.resolve(false);
275-
} else {
272+
if (Platform.OS === 'android') {
273+
return Promise.resolve(false);
274+
} else {
275+
return new Promise((resolve, reject) => {
276276
if (
277277
NativeAccessibilityManagerIOS?.getCurrentPrefersCrossFadeTransitionsState !=
278278
null
@@ -288,8 +288,8 @@ const AccessibilityInfo = {
288288
),
289289
);
290290
}
291-
}
292-
});
291+
});
292+
}
293293
},
294294

295295
/**
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
jest.unmock('../AccessibilityInfo');
12+
13+
const mockGetCurrentPrefersCrossFadeTransitionsState = jest.fn(
14+
(onSuccess, onError) => onSuccess(true),
15+
);
16+
const mockNativeAccessibilityManagerDefault: {
17+
getCurrentPrefersCrossFadeTransitionsState: JestMockFn<
18+
[
19+
onSuccess: (prefersCrossFadeTransitions: boolean) => void,
20+
onError: (error: Error) => void,
21+
],
22+
void,
23+
> | null,
24+
} = {
25+
getCurrentPrefersCrossFadeTransitionsState:
26+
mockGetCurrentPrefersCrossFadeTransitionsState,
27+
};
28+
29+
jest.mock('../NativeAccessibilityManager', () => ({
30+
__esModule: true,
31+
default: mockNativeAccessibilityManagerDefault,
32+
}));
33+
34+
const Platform = require('../../../Utilities/Platform').default;
35+
const AccessibilityInfo = require('../AccessibilityInfo').default;
36+
const invariant = require('invariant');
37+
38+
describe('AccessibilityInfo', () => {
39+
let originalPlatform;
40+
41+
beforeEach(() => {
42+
originalPlatform = Platform.OS;
43+
mockGetCurrentPrefersCrossFadeTransitionsState.mockClear();
44+
});
45+
46+
describe('prefersCrossFadeTransitions', () => {
47+
describe('Android', () => {
48+
it('should return immediately', async () => {
49+
/* $FlowFixMe[incompatible-type] */
50+
Platform.OS = 'android';
51+
52+
const prefersCrossFadeTransitions =
53+
await AccessibilityInfo.prefersCrossFadeTransitions();
54+
55+
expect(prefersCrossFadeTransitions).toBe(false);
56+
});
57+
});
58+
59+
describe('iOS', () => {
60+
it('should call getCurrentPrefersCrossFadeTransitionsState if available', async () => {
61+
/* $FlowFixMe[incompatible-type] */
62+
Platform.OS = 'ios';
63+
64+
const prefersCrossFadeTransitions =
65+
await AccessibilityInfo.prefersCrossFadeTransitions();
66+
67+
expect(
68+
mockGetCurrentPrefersCrossFadeTransitionsState,
69+
).toHaveBeenCalled();
70+
expect(prefersCrossFadeTransitions).toBe(true);
71+
});
72+
73+
it('should throw error if getCurrentPrefersCrossFadeTransitionsState is not available', async () => {
74+
/* $FlowFixMe[incompatible-type] */
75+
Platform.OS = 'ios';
76+
77+
mockNativeAccessibilityManagerDefault.getCurrentPrefersCrossFadeTransitionsState =
78+
null;
79+
80+
const result: mixed =
81+
await AccessibilityInfo.prefersCrossFadeTransitions().catch(e => e);
82+
83+
invariant(
84+
result instanceof Error,
85+
'Expected prefersCrossFadeTransitions to reject',
86+
);
87+
expect(result.message).toEqual(
88+
'NativeAccessibilityManagerIOS.getCurrentPrefersCrossFadeTransitionsState is not available',
89+
);
90+
});
91+
});
92+
});
93+
94+
afterEach(() => {
95+
mockNativeAccessibilityManagerDefault.getCurrentPrefersCrossFadeTransitionsState =
96+
mockGetCurrentPrefersCrossFadeTransitionsState;
97+
/* $FlowFixMe[incompatible-type] */
98+
Platform.OS = originalPlatform;
99+
});
100+
});

0 commit comments

Comments
 (0)