diff --git a/jestSetup/jest-setup.js b/jestSetup/jest-setup.js index 177b6f4721..68eba4c555 100644 --- a/jestSetup/jest-setup.js +++ b/jestSetup/jest-setup.js @@ -19,6 +19,7 @@ Object.defineProperties = (obj, props) => { global._UILIB_TESTING = true; jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockImplementation(() => Promise.resolve(false)); +jest.spyOn(AccessibilityInfo, 'isReduceMotionEnabled').mockImplementation(() => Promise.resolve(false)); // mock native modules jest.mock('@react-native-community/blur', () => {}); @@ -98,6 +99,15 @@ jest.mock('react-native', () => { const reactNative = jest.requireActual('react-native'); reactNative.NativeModules.KeyboardTrackingViewTempManager = {}; reactNative.NativeModules.StatusBarManager = {getHeight: jest.fn()}; + + reactNative.AccessibilityInfo = { + ...reactNative.AccessibilityInfo, + isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)), + isReduceMotionEnabled: jest.fn(() => Promise.resolve(false)), + addEventListener: jest.fn(), + removeEventListener: jest.fn() + }; + const OriginalModal = reactNative.Modal; const React = jest.requireActual('react'); const useDidUpdate = require('./useDidUpdate').default; @@ -152,3 +162,7 @@ jest.mock('../src/optionalDependencies', () => { DateTimePickerPackage: view }; }); + +// Ensure AccessibilityInfo mocks are available globally after react-native mock +const RN = require('react-native'); +global.AccessibilityInfo = RN.AccessibilityInfo; diff --git a/src/commons/Constants.ts b/src/commons/Constants.ts index 7f62e63633..d016ea8647 100644 --- a/src/commons/Constants.ts +++ b/src/commons/Constants.ts @@ -67,19 +67,28 @@ export function updateConstants(dimensions: any) { } const accessibility = { - isScreenReaderEnabled: false + isScreenReaderEnabled: false, + isReduceMotionEnabled: false }; function handleScreenReaderChanged(isScreenReaderEnabled: AccessibilityChangeEvent) { accessibility.isScreenReaderEnabled = isScreenReaderEnabled as boolean; } +function handleReduceMotionChanged(isReduceMotionEnabled: AccessibilityChangeEvent) { + accessibility.isReduceMotionEnabled = isReduceMotionEnabled as boolean; +} + AccessibilityInfo.addEventListener('screenReaderChanged', handleScreenReaderChanged); +AccessibilityInfo.addEventListener('reduceMotionChanged', handleReduceMotionChanged); function setAccessibility() { AccessibilityInfo.isScreenReaderEnabled().then(isScreenReaderEnabled => { accessibility.isScreenReaderEnabled = isScreenReaderEnabled; }); + AccessibilityInfo.isReduceMotionEnabled().then(isReduceMotionEnabled => { + accessibility.isReduceMotionEnabled = isReduceMotionEnabled; + }); } setAccessibility(); diff --git a/src/hooks/useOrientation/__tests__/useOrientation.spec.js b/src/hooks/useOrientation/__tests__/useOrientation.spec.js index 8e7345c1dc..fc86c34388 100644 --- a/src/hooks/useOrientation/__tests__/useOrientation.spec.js +++ b/src/hooks/useOrientation/__tests__/useOrientation.spec.js @@ -1,20 +1,33 @@ import {renderHook, act} from '@testing-library/react-hooks'; +jest.mock('../../../commons/new', () => { + return { + Constants: { + orientation: 'portrait', + addDimensionsEventListener: jest.fn(), + removeDimensionsEventListener: jest.fn() + } + }; +}); + let Constants; let useOrientation; let orientationChangeListeners; describe('useOrientation hook', () => { beforeEach(() => { - jest.mock('../../../commons/Constants'); - Constants = require('../../../commons/Constants').default; + const {Constants: MockedConstants} = require('../../../commons/new'); + Constants = MockedConstants; useOrientation = require('../index').default; orientationChangeListeners = []; Constants.addDimensionsEventListener = jest.fn(callback => { orientationChangeListeners.push(callback); + return {remove: jest.fn()}; }); + + Constants.removeDimensionsEventListener = jest.fn(); }); it('should return current orientation', () => { diff --git a/src/incubator/dialog/__tests__/index.new.spec.tsx b/src/incubator/dialog/__tests__/index.new.spec.tsx index 52bc002e7f..c37b859dc6 100644 --- a/src/incubator/dialog/__tests__/index.new.spec.tsx +++ b/src/incubator/dialog/__tests__/index.new.spec.tsx @@ -1,5 +1,6 @@ import React, {useRef, useState, useEffect, useCallback} from 'react'; import {render, act} from '@testing-library/react-native'; +import {AccessibilityInfo} from 'react-native'; import Dialog, {DialogProps} from '../index'; import {DialogDriver} from '../Dialog.driver.new'; import View from '../../../components/view'; @@ -22,7 +23,7 @@ const defaultProps = { centerH: true }; -const TestCase2 = props => { +const TestCase2 = (props: any) => { const [visible, setVisible] = useState(props.visible); useEffect(() => { @@ -108,4 +109,16 @@ describe('Incubator.Dialog sanity checks', () => { expect(dialogDriver.exists()).toBeFalsy(); expect(dialogDriver.isVisible()).toBeFalsy(); }); + + it('Should show and dismiss dialog immediately when reduce motion is enabled', async () => { + jest.spyOn(AccessibilityInfo, 'isReduceMotionEnabled').mockResolvedValue(true); + const dismissFn = jest.fn(); + const {dialogDriver} = getDriver(); + expect(dialogDriver.isVisible()).toBeTruthy(); + expect(dismissFn).not.toHaveBeenCalled(); + dialogDriver.pressOnBackground(); + expect(dialogDriver.isVisible()).toBeFalsy(); + expect(dismissFn).toHaveBeenCalledTimes(1); + jest.restoreAllMocks(); + }); }); diff --git a/src/incubator/dialog/index.tsx b/src/incubator/dialog/index.tsx index bf64bb2856..6f46f52a78 100644 --- a/src/incubator/dialog/index.tsx +++ b/src/incubator/dialog/index.tsx @@ -91,13 +91,22 @@ const Dialog = (props: DialogProps, ref: ForwardedRef) const close = useCallback(() => { 'worklet'; - visibility.value = withTiming(0, undefined, _onDismiss); + if (Constants.accessibility.isReduceMotionEnabled) { + visibility.value = 0; + _onDismiss(); + } else { + visibility.value = withTiming(0, undefined, _onDismiss); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [_onDismiss]); const open = useCallback(() => { 'worklet'; - visibility.value = withSpring(1); + if (Constants.accessibility.isReduceMotionEnabled) { + visibility.value = 1; + } else { + visibility.value = withSpring(1); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/style/__tests__/scheme.spec.js b/src/style/__tests__/scheme.spec.js index 0bab455704..5986241e14 100644 --- a/src/style/__tests__/scheme.spec.js +++ b/src/style/__tests__/scheme.spec.js @@ -1,7 +1,13 @@ +import {AccessibilityInfo} from 'react-native'; + let Scheme; describe('Scheme', () => { beforeEach(() => { jest.resetModules(); + // Re-apply the AccessibilityInfo mocks after resetModules + jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockImplementation(() => Promise.resolve(false)); + jest.spyOn(AccessibilityInfo, 'isReduceMotionEnabled').mockImplementation(() => Promise.resolve(false)); + Scheme = require('../scheme').default; });