Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/jest-preset/jest/mocks/AccessibilityInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const AccessibilityInfo = {
isAccessibilityServiceEnabled: jest.fn(() =>
Promise.resolve(false),
) as JestMockFn<$FlowFixMe, $FlowFixMe>,
isDifferentiateWithoutColorEnabled: jest.fn(() =>
Promise.resolve(false),
) as JestMockFn<$FlowFixMe, $FlowFixMe>,
isBoldTextEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn<
$FlowFixMe,
$FlowFixMe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type AccessibilityEventDefinitionsIOS = {
boldTextChanged: [boolean],
darkerSystemColorsChanged: [boolean],
reduceTransparencyChanged: [boolean],
differentiateWithoutColorChanged: [boolean],
};

type AccessibilityEventDefinitions = {
Expand Down Expand Up @@ -67,6 +68,10 @@ const EventNames: Map<keyof AccessibilityEventDefinitions, string> =
['reduceTransparencyChanged', 'reduceTransparencyChanged'],
['screenReaderChanged', 'screenReaderChanged'],
['darkerSystemColorsChanged', 'darkerSystemColorsChanged'],
[
'differentiateWithoutColorChanged',
'differentiateWithoutColorChanged',
],
]);

/**
Expand Down Expand Up @@ -287,6 +292,38 @@ const AccessibilityInfo = {
}
},

/**
* Query whether differentiate without color is currently enabled.
*
* Returns a promise which resolves to a boolean.
* The result is `true` when differentiate without color is enabled and `false` otherwise.
*
* @platform ios
*/
isDifferentiateWithoutColorEnabled(): Promise<boolean> {
if (Platform.OS === 'android') {
return Promise.resolve(false);
} else {
return new Promise((resolve, reject) => {
if (
NativeAccessibilityManagerIOS?.getCurrentDifferentiateWithoutColorState !=
null
) {
NativeAccessibilityManagerIOS.getCurrentDifferentiateWithoutColorState(
resolve,
reject,
);
} else {
reject(
new Error(
'NativeAccessibilityManagerIOS.getCurrentDifferentiateWithoutColorState is not available',
),
);
}
});
}
},

/**
* Query whether reduced transparency is currently enabled.
*
Expand Down Expand Up @@ -407,6 +444,9 @@ const AccessibilityInfo = {
* toggle changes. The argument to the event handler is a boolean. The boolean is `true` when
* dark system colors is enabled and `false` otherwise.
*
* - `differentiateWithoutColorChanged`: iOS-only event. Fires when the state of the differentiate without color toggle changes.
* The argument to the event handler is a boolean.
*
* These events are only supported on Android:
*
* - `highTextContrastChanged`: Android-only event. Fires when the state of the high text contrast
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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 differentiateWithoutColorChanged', () => {
it('delivers differentiateWithoutColorChanged on iOS', () => {
const {AccessibilityInfo, RCTDeviceEventEmitter} = loadForPlatform('ios');
const handler = jest.fn();

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

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

it('does not subscribe on Android', () => {
const {AccessibilityInfo, RCTDeviceEventEmitter} =
loadForPlatform('android');
const handler = jest.fn();

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

expect(handler).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const mockGetCurrentPrefersCrossFadeTransitionsState = jest.fn(
const mockGetCurrentDarkerSystemColorsState = jest.fn((onSuccess, onError) =>
onSuccess(true),
);
const mockGetCurrentDifferentiateWithoutColorState = jest.fn(
(onSuccess, onError) => onSuccess(true),
);
const mockGetCurrentBoldTextState = jest.fn((onSuccess, onError) =>
onSuccess(true),
);
Expand All @@ -35,6 +38,13 @@ const mockGetCurrentVoiceOverState = jest.fn((onSuccess, onError) =>
onSuccess(true),
);
let mockNativeAccessibilityManagerIOS: {
getCurrentDifferentiateWithoutColorState: JestMockFn<
[
onSuccess: (isDifferentiateWithoutColorEnabled: boolean) => void,
onError: (error: Error) => void,
],
void,
> | null,
getCurrentPrefersCrossFadeTransitionsState: JestMockFn<
[
onSuccess: (prefersCrossFadeTransitions: boolean) => void,
Expand Down Expand Up @@ -92,6 +102,8 @@ let mockNativeAccessibilityManagerIOS: {
void,
> | null,
} = {
getCurrentDifferentiateWithoutColorState:
mockGetCurrentDifferentiateWithoutColorState,
getCurrentPrefersCrossFadeTransitionsState:
mockGetCurrentPrefersCrossFadeTransitionsState,
getCurrentDarkerSystemColorsState: mockGetCurrentDarkerSystemColorsState,
Expand Down Expand Up @@ -660,6 +672,54 @@ describe('AccessibilityInfo', () => {
});
});

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

const result =
await AccessibilityInfo.isDifferentiateWithoutColorEnabled();

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

describe('iOS', () => {
it('should call getCurrentDifferentiateWithoutColorState if available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'ios';

const result =
await AccessibilityInfo.isDifferentiateWithoutColorEnabled();

expect(mockGetCurrentDifferentiateWithoutColorState).toHaveBeenCalled();
expect(result).toBe(true);
});

it('should throw error if getCurrentDifferentiateWithoutColorState is not available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'ios';

mockNativeAccessibilityManagerIOS.getCurrentDifferentiateWithoutColorState =
null;

const result: unknown =
await AccessibilityInfo.isDifferentiateWithoutColorEnabled().catch(
e => e,
);

invariant(
result instanceof Error,
'Expected isDifferentiateWithoutColorEnabled to reject',
);
expect(result.message).toEqual(
'NativeAccessibilityManagerIOS.getCurrentDifferentiateWithoutColorState is not available',
);
});
});
});

describe('isDarkerSystemColorsEnabled', () => {
describe('Android', () => {
it('should return immediately', async () => {
Expand Down Expand Up @@ -791,6 +851,8 @@ describe('AccessibilityInfo', () => {

afterEach(() => {
mockNativeAccessibilityManagerIOS = {
getCurrentDifferentiateWithoutColorState:
mockGetCurrentDifferentiateWithoutColorState,
getCurrentPrefersCrossFadeTransitionsState:
mockGetCurrentPrefersCrossFadeTransitionsState,
getCurrentDarkerSystemColorsState: mockGetCurrentDarkerSystemColorsState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; /
@property (nonatomic, assign) BOOL isDarkerSystemColorsEnabled;
@property (nonatomic, assign) BOOL prefersCrossFadeTransitions;
@property (nonatomic, assign) BOOL isReduceTransparencyEnabled;
@property (nonatomic, assign) BOOL isDifferentiateWithoutColorEnabled;
@property (nonatomic, assign) BOOL isVoiceOverEnabled;

@end
Expand Down
26 changes: 26 additions & 0 deletions packages/react-native/React/CoreModules/RCTAccessibilityManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ - (instancetype)init
name:UIAccessibilityReduceTransparencyStatusDidChangeNotification
object:nil];

[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(differentiateWithoutColorStatusDidChange:)
name:UIAccessibilityShouldDifferentiateWithoutColorDidChangeNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(voiceVoiceOverStatusDidChange:)
name:UIAccessibilityVoiceOverStatusDidChangeNotification
Expand All @@ -103,6 +109,7 @@ - (instancetype)init
_isReduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled();
_isDarkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled();
_isReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled();
_isDifferentiateWithoutColorEnabled = UIAccessibilityShouldDifferentiateWithoutColor();
_isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning();
}
return self;
Expand Down Expand Up @@ -207,6 +214,19 @@ - (void)reduceTransparencyStatusDidChange:(__unused NSNotification *)notificatio
}
}

- (void)differentiateWithoutColorStatusDidChange:(__unused NSNotification *)notification
{
BOOL newValue = UIAccessibilityShouldDifferentiateWithoutColor();
if (_isDifferentiateWithoutColorEnabled != newValue) {
_isDifferentiateWithoutColorEnabled = newValue;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"differentiateWithoutColorChanged"
body:@(_isDifferentiateWithoutColorEnabled)];
#pragma clang diagnostic pop
}
}

- (void)voiceVoiceOverStatusDidChange:(__unused NSNotification *)notification
{
BOOL isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning();
Expand Down Expand Up @@ -411,6 +431,12 @@ - (void)getCurrentReduceTransparencyState:(RCTResponseSenderBlock)onSuccess
onSuccess(@[ @(_isReduceTransparencyEnabled) ]);
}

- (void)getCurrentDifferentiateWithoutColorState:(RCTResponseSenderBlock)onSuccess
onError:(__unused RCTResponseSenderBlock)onError
{
onSuccess(@[ @(_isDifferentiateWithoutColorEnabled) ]);
}

- (void)getCurrentVoiceOverState:(RCTResponseSenderBlock)onSuccess onError:(__unused RCTResponseSenderBlock)onError
{
onSuccess(@[ @(_isVoiceOverEnabled) ]);
Expand Down
6 changes: 4 additions & 2 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<b0c8c41e65758fde472ddf1c5b106a27>>
* @generated SignedSource<<4a4f2da4bdd6b014a0b1cf24dc1318e9>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -95,6 +95,7 @@ declare const AccessibilityInfo_default: {
isAccessibilityServiceEnabled(): Promise<boolean>
isBoldTextEnabled(): Promise<boolean>
isDarkerSystemColorsEnabled(): Promise<boolean>
isDifferentiateWithoutColorEnabled(): Promise<boolean>
isGrayscaleEnabled(): Promise<boolean>
isHighTextContrastEnabled(): Promise<boolean>
isInvertColorsEnabled(): Promise<boolean>
Expand Down Expand Up @@ -973,6 +974,7 @@ declare type AccessibilityEventDefinitionsIOS = {
]
boldTextChanged: [boolean]
darkerSystemColorsChanged: [boolean]
differentiateWithoutColorChanged: [boolean]
reduceTransparencyChanged: [boolean]
}
declare type AccessibilityEventTypes =
Expand Down Expand Up @@ -5770,7 +5772,7 @@ declare type WrapperComponentProvider = (
export {
AccessibilityActionEvent, // a276ec6e
AccessibilityActionInfo, // c94545a7
AccessibilityInfo, // d92c5eae
AccessibilityInfo, // c3f5c938
AccessibilityProps, // 95dbb1d8
AccessibilityRole, // f2f2e066
AccessibilityState, // b0c2b3f7
Expand Down
12 changes: 12 additions & 0 deletions packages/react-native/__typetests__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,18 @@ class AccessibilityTest extends React.Component {
}
}

AccessibilityInfo.isDifferentiateWithoutColorEnabled().then(isEnabled =>
console.log(
`AccessibilityInfo.isDifferentiateWithoutColorEnabled => ${isEnabled}`,
),
);
AccessibilityInfo.addEventListener(
'differentiateWithoutColorChanged',
isEnabled =>
console.log(
`AccessibilityInfo.differentiateWithoutColorChanged => ${isEnabled}`,
),
);
AccessibilityInfo.isBoldTextEnabled().then(isEnabled =>
console.log(`AccessibilityInfo.isBoldTextEnabled => ${isEnabled}`),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export interface Spec extends TurboModule {
onSuccess: (prefersCrossFadeTransitions: boolean) => void,
onError: (error: UnsafeObject) => void,
) => void;
readonly getCurrentDifferentiateWithoutColorState?: (
onSuccess: (isDifferentiateWithoutColorEnabled: boolean) => void,
onError: (error: UnsafeObject) => void,
) => void;
readonly getCurrentReduceTransparencyState: (
onSuccess: (isReduceTransparencyEnabled: boolean) => void,
onError: (error: UnsafeObject) => void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ type AccessibilityChangeEventName =
| 'reduceMotionChanged'
/** iOS only */
| 'reduceTransparencyChanged'
| 'screenReaderChanged';
| 'screenReaderChanged'
/** iOS only */
| 'differentiateWithoutColorChanged';

type AccessibilityChangeEvent = boolean;

Expand Down Expand Up @@ -86,6 +88,13 @@ export interface AccessibilityInfoStatic {
*/
isHighTextContrastEnabled: () => Promise<boolean>;

/**
* Query whether differentiate without color is currently enabled.
*
* @platform ios
*/
isDifferentiateWithoutColorEnabled: () => Promise<boolean>;

/**
* Query whether darker system colors is currently enabled.
*
Expand Down
Loading
Loading