Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ type AccessibilityEventDefinitionsAndroid = {
// Events that are only supported on iOS.
type AccessibilityEventDefinitionsIOS = {
announcementFinished: [{announcement: string, success: boolean}],
boldTextChanged: [boolean],
darkerSystemColorsChanged: [boolean],
reduceTransparencyChanged: [boolean],
showBordersChanged: [boolean],
Expand All @@ -36,6 +35,7 @@ type AccessibilityEventDefinitionsIOS = {
type AccessibilityEventDefinitions = {
...AccessibilityEventDefinitionsAndroid,
...AccessibilityEventDefinitionsIOS,
boldTextChanged: [boolean],
change: [boolean], // screenReaderChanged
grayscaleChanged: [boolean],
invertColorsChanged: [boolean],
Expand All @@ -53,6 +53,7 @@ const EventNames: Map<keyof AccessibilityEventDefinitions, string> =
['change', 'touchExplorationDidChange'],
['reduceMotionChanged', 'reduceMotionDidChange'],
['highTextContrastChanged', 'highTextContrastDidChange'],
['boldTextChanged', 'boldTextDidChange'],
['screenReaderChanged', 'touchExplorationDidChange'],
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
['invertColorsChanged', 'invertColorDidChange'],
Expand Down Expand Up @@ -83,11 +84,22 @@ const AccessibilityInfo = {
* Returns a promise which resolves to a boolean.
* The result is `true` when bold text is enabled and `false` otherwise.
*
* @platform ios
* On Android, bold text is supported on Android 12 (API 31) and later;
* earlier versions always resolve to `false`.
*/
isBoldTextEnabled(): Promise<boolean> {
if (Platform.OS === 'android') {
return Promise.resolve(false);
return new Promise(resolve => {
if (NativeAccessibilityInfoAndroid?.isBoldTextEnabled != null) {
NativeAccessibilityInfoAndroid.isBoldTextEnabled(resolve);
} else {
// `isBoldTextEnabled` is an existing API that resolved `false` on
// Android before native support was added, so it keeps resolving
// `false` when the native module or method is missing. APIs that are
// new reject instead, like `isShowBordersEnabled`.
resolve(false);
}
});
} else {
return new Promise((resolve, reject) => {
if (NativeAccessibilityManagerIOS != null) {
Expand Down Expand Up @@ -420,12 +432,12 @@ const AccessibilityInfo = {
* - `invertColorsChanged`: Fires when the state of the invert colors toggle
* changes. The argument to the event handler is a boolean. The boolean is `true` when invert
* colors is enabled and `false` otherwise.
* - `boldTextChanged`: Fires when the state of the bold text toggle changes.
* The argument to the event handler is a boolean. The boolean is `true` when bold text
* is enabled and `false` otherwise. On Android, requires Android 12 (API 31) or later.
*
* These events are only supported on iOS:
*
* - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes.
* The argument to the event handler is a boolean. The boolean is `true` when a bold text
* is enabled and `false` otherwise.
* - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency
* toggle changes. The argument to the event handler is a boolean. The boolean is `true`
* when a reduce transparency is enabled and `false` otherwise.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import typeof RCTDeviceEventEmitterType from '../../../EventEmitter/RCTDeviceEventEmitter';
import typeof AccessibilityInfoType from '../AccessibilityInfo';

const invariant = require('invariant');

jest.unmock('../AccessibilityInfo');

type LoadedModules = {
AccessibilityInfo: AccessibilityInfoType,
RCTDeviceEventEmitter: RCTDeviceEventEmitterType,
};

// `AccessibilityInfo` picks its native event names when it is first loaded,
// so each platform needs a fresh module registry.
function loadForPlatform(os: 'android' | 'ios'): LoadedModules {
let modules: ?LoadedModules = null;
jest.isolateModules(() => {
const Platform = require('../../../Utilities/Platform').default;
/* $FlowFixMe[incompatible-type] */
Platform.OS = os;
modules = {
AccessibilityInfo: require('../AccessibilityInfo').default,
RCTDeviceEventEmitter:
require('../../../EventEmitter/RCTDeviceEventEmitter').default,
};
});
invariant(modules != null, 'Expected modules to load');
return modules;
}

describe('AccessibilityInfo events', () => {
describe('Android', () => {
it('delivers boldTextDidChange as boldTextChanged', () => {
const {AccessibilityInfo, RCTDeviceEventEmitter} =
loadForPlatform('android');
const handler = jest.fn();

const subscription = AccessibilityInfo.addEventListener(
'boldTextChanged',
handler,
);
RCTDeviceEventEmitter.emit('boldTextDidChange', true);
subscription.remove();
RCTDeviceEventEmitter.emit('boldTextDidChange', false);

expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith(true);
});
});

describe('iOS', () => {
it('delivers boldTextChanged as boldTextChanged', () => {
const {AccessibilityInfo, RCTDeviceEventEmitter} = loadForPlatform('ios');
const handler = jest.fn();

const subscription = AccessibilityInfo.addEventListener(
'boldTextChanged',
handler,
);
RCTDeviceEventEmitter.emit('boldTextChanged', true);
subscription.remove();
RCTDeviceEventEmitter.emit('boldTextChanged', false);

expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ let mockNativeAccessibilityManagerIOS: {
};

const ANDROID_RECOMMENDED_TIMEOUT = 6000;
const mockIsBoldTextEnabledAndroid = jest.fn(onSuccess => onSuccess(true));
const mockIsHighTextContrastEnabled = jest.fn(onSuccess => onSuccess(true));
const mockIsGrayscaleEnabled = jest.fn(onSuccess => onSuccess(true));
const mockIsInvertColorsEnabled = jest.fn(onSuccess => onSuccess(true));
Expand All @@ -125,6 +126,10 @@ const mockGetRecommendedTimeoutMillis = jest.fn((originalTimeout, onSuccess) =>
onSuccess(ANDROID_RECOMMENDED_TIMEOUT),
);
let mockNativeAccessibilityInfoAndroid: {
isBoldTextEnabled: JestMockFn<
[onSuccess: (isBoldTextEnabled: boolean) => void],
void,
> | null,
isHighTextContrastEnabled: JestMockFn<
[onSuccess: (isHighTextContrastEnabled: boolean) => void],
void,
Expand Down Expand Up @@ -154,6 +159,7 @@ let mockNativeAccessibilityInfoAndroid: {
void,
> | null,
} = {
isBoldTextEnabled: mockIsBoldTextEnabledAndroid,
isHighTextContrastEnabled: mockIsHighTextContrastEnabled,
isGrayscaleEnabled: mockIsGrayscaleEnabled,
isInvertColorsEnabled: mockIsInvertColorsEnabled,
Expand Down Expand Up @@ -187,12 +193,39 @@ describe('AccessibilityInfo', () => {

describe('isBoldTextEnabled', () => {
describe('Android', () => {
it('should return immediately', async () => {
it('should call isBoldTextEnabled if available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

const isBoldTextEnabled = await AccessibilityInfo.isBoldTextEnabled();

expect(mockIsBoldTextEnabledAndroid).toHaveBeenCalled();
expect(isBoldTextEnabled).toBe(true);
});

it('should return false if isBoldTextEnabled is not available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

mockNativeAccessibilityInfoAndroid.isBoldTextEnabled = null;

const isBoldTextEnabled = await AccessibilityInfo.isBoldTextEnabled();

expect(isBoldTextEnabled).toBe(false);
});

it('should return false if NativeAccessibilityInfoAndroid is not available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

const nativeAccessibilityInfoModule = jest.requireMock(
'../NativeAccessibilityInfo',
);
nativeAccessibilityInfoModule.default = null;

const isBoldTextEnabled = await AccessibilityInfo.isBoldTextEnabled();

expect(mockIsBoldTextEnabledAndroid).not.toHaveBeenCalled();
expect(isBoldTextEnabled).toBe(false);
});
});
Expand Down Expand Up @@ -860,6 +893,7 @@ describe('AccessibilityInfo', () => {
mockNativeAccessibilityManagerIOS;

mockNativeAccessibilityInfoAndroid = {
isBoldTextEnabled: mockIsBoldTextEnabledAndroid,
isHighTextContrastEnabled: mockIsHighTextContrastEnabled,
isGrayscaleEnabled: mockIsGrayscaleEnabled,
isInvertColorsEnabled: mockIsInvertColorsEnabled,
Expand Down
Loading
Loading