88
99import { render , act } from '@testing-library/react-native' ;
1010
11+ import { useReduceMotion } from '../../theme/accessibility/ReduceMotionContext' ;
1112import { LightTheme , DarkTheme } from '../../theme/schemes' ;
1213import type { ThemeProp } from '../../types' ;
1314import PaperProvider from '../PaperProvider' ;
@@ -16,9 +17,7 @@ import { useTheme } from '../theming';
1617declare module 'react-native' {
1718 interface AccessibilityInfoStatic {
1819 removeEventListener ( ) : void ;
19- __internalListeners : Array <
20- ( options : { reduceMotionEnabled : boolean } ) => { }
21- > ;
20+ __internalListeners : Array < ( enabled : boolean ) => void > ;
2221 }
2322
2423 namespace Appearance {
@@ -38,6 +37,7 @@ declare module 'react-native' {
3837
3938 interface ViewProps {
4039 theme ?: object ;
40+ reduceMotion ?: boolean ;
4141 }
4242}
4343
@@ -82,6 +82,7 @@ const mockAccessibilityInfo = () => {
8282 removeEventListener : jest . fn ( ( cb ) => {
8383 listeners . push ( cb ) ;
8484 } ) ,
85+ isReduceMotionEnabled : jest . fn ( ( ) => Promise . resolve ( false ) ) ,
8586 __internalListeners : listeners ,
8687 } ,
8788 } ;
@@ -122,36 +123,94 @@ describe('PaperProvider', () => {
122123 ) ;
123124 } ) ;
124125
125- it ( 'should set AccessibilityInfo listeners, if there is no theme ' , async ( ) => {
126+ it ( 'subscribes to AccessibilityInfo and adapts theme.animation.scale when OS reduce-motion is enabled (auto mode) ' , async ( ) => {
126127 mockAppearance ( ) ;
127128 mockAccessibilityInfo ( ) ;
128129
129- const { rerender , getByTestId } = render ( createProvider ( ) ) ;
130+ const { getByTestId } = render ( createProvider ( ) ) ;
130131
131132 expect ( AccessibilityInfo . addEventListener ) . toHaveBeenCalled ( ) ;
132- act ( ( ) =>
133- AccessibilityInfo . __internalListeners [ 0 ] ( {
134- reduceMotionEnabled : true ,
135- } )
136- ) ;
133+ act ( ( ) => AccessibilityInfo . __internalListeners [ 0 ] ( true ) ) ;
137134
138135 expect (
139136 getByTestId ( 'provider-child-view' ) . props . theme . animation . scale
140137 ) . toStrictEqual ( 0 ) ;
138+ } ) ;
139+
140+ it ( 'exposes the resolved reduce-motion boolean via useReduceMotion to children' , async ( ) => {
141+ mockAppearance ( ) ;
142+ mockAccessibilityInfo ( ) ;
143+
144+ const Probe = ( ) => {
145+ const reduceMotion = useReduceMotion ( ) ;
146+ return < View testID = "reduce-motion-probe" reduceMotion = { reduceMotion } /> ;
147+ } ;
148+
149+ const { getByTestId, rerender } = render (
150+ < PaperProvider reduceMotion = "on" >
151+ < Probe />
152+ </ PaperProvider >
153+ ) ;
154+ expect ( getByTestId ( 'reduce-motion-probe' ) . props . reduceMotion ) . toBe ( true ) ;
141155
142- rerender ( createProvider ( ExtendedLightTheme ) ) ;
143- expect ( AccessibilityInfo . removeEventListener ) . toHaveBeenCalled ( ) ;
156+ rerender (
157+ < PaperProvider reduceMotion = "off" >
158+ < Probe />
159+ </ PaperProvider >
160+ ) ;
161+ expect ( getByTestId ( 'reduce-motion-probe' ) . props . reduceMotion ) . toBe ( false ) ;
144162 } ) ;
145163
146- it ( 'should not set AccessibilityInfo listeners, if there is a theme ' , async ( ) => {
164+ it ( 'removes the AccessibilityInfo listener when reduceMotion switches from "auto" to "off" ' , async ( ) => {
147165 mockAppearance ( ) ;
148- const { getByTestId } = render ( createProvider ( ExtendedDarkTheme ) ) ;
166+ mockAccessibilityInfo ( ) ;
149167
150- expect ( AccessibilityInfo . addEventListener ) . not . toHaveBeenCalled ( ) ;
168+ const { rerender } = render (
169+ < PaperProvider reduceMotion = "auto" >
170+ < FakeChild />
171+ </ PaperProvider >
172+ ) ;
173+
174+ expect ( AccessibilityInfo . addEventListener ) . toHaveBeenCalledTimes ( 1 ) ;
151175 expect ( AccessibilityInfo . removeEventListener ) . not . toHaveBeenCalled ( ) ;
152- expect ( getByTestId ( 'provider-child-view' ) . props . theme ) . toStrictEqual (
153- ExtendedDarkTheme
176+
177+ rerender (
178+ < PaperProvider reduceMotion = "off" >
179+ < FakeChild />
180+ </ PaperProvider >
154181 ) ;
182+
183+ expect ( AccessibilityInfo . removeEventListener ) . toHaveBeenCalledTimes ( 1 ) ;
184+ } ) ;
185+
186+ it ( 'does not subscribe to AccessibilityInfo when reduceMotion is "off"' , async ( ) => {
187+ mockAppearance ( ) ;
188+ mockAccessibilityInfo ( ) ;
189+ const { getByTestId } = render (
190+ < PaperProvider theme = { ExtendedDarkTheme } reduceMotion = "off" >
191+ < FakeChild />
192+ </ PaperProvider >
193+ ) ;
194+
195+ expect ( AccessibilityInfo . addEventListener ) . not . toHaveBeenCalled ( ) ;
196+ expect (
197+ getByTestId ( 'provider-child-view' ) . props . theme . animation . scale
198+ ) . toStrictEqual ( 1 ) ;
199+ } ) ;
200+
201+ it ( 'forces animation.scale to 0 when reduceMotion is "on" without subscribing' , async ( ) => {
202+ mockAppearance ( ) ;
203+ mockAccessibilityInfo ( ) ;
204+ const { getByTestId } = render (
205+ < PaperProvider reduceMotion = "on" >
206+ < FakeChild />
207+ </ PaperProvider >
208+ ) ;
209+
210+ expect ( AccessibilityInfo . addEventListener ) . not . toHaveBeenCalled ( ) ;
211+ expect (
212+ getByTestId ( 'provider-child-view' ) . props . theme . animation . scale
213+ ) . toStrictEqual ( 0 ) ;
155214 } ) ;
156215
157216 it ( 'should set Appearance listeners, if there is no theme' , async ( ) => {
0 commit comments