Skip to content

Commit

Permalink
Fix inline style tests in Jest (#6400)
Browse files Browse the repository at this point in the history
## Summary

It was brought to our attention that `inline styles` do not work with
`Jest`. This PR aims to fix this problem.

## Test plan

Run tests
  • Loading branch information
m-bert authored Aug 21, 2024
1 parent 2e6b558 commit 00f074f
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Button, View } from 'react-native';
import { fireEvent, render, screen } from '@testing-library/react-native';
import Animated, {
useSharedValue,
getAnimatedStyle,
useAnimatedStyle,
withTiming,
} from '../src';

jest.useFakeTimers();

describe('Tests of inline styles', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

test('useAnimatedStyle', () => {
function UseAnimatedStyle() {
const width = useSharedValue(100);

const handlePress = () => {
width.value = width.value + 50;
};

const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
}, [width]);

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View testID="view" style={animatedStyle} />
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<UseAnimatedStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100 });

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({ width: 150 });
});

test('useAnimatedStyle with withTiming', () => {
function UseAnimatedStyle() {
const width = useSharedValue(100);

const handlePress = () => {
width.value = withTiming(width.value + 50, { duration: 500 });
};

const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
}, [width]);

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View testID="view" style={animatedStyle} />
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<UseAnimatedStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100 });

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({ width: 150 });
});
});
197 changes: 197 additions & 0 deletions packages/react-native-reanimated/__tests__/inlineStyles.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { Button, View } from 'react-native';
import { fireEvent, render, screen } from '@testing-library/react-native';
import Animated, {
useSharedValue,
getAnimatedStyle,
useAnimatedStyle,
withTiming,
} from '../src';

jest.useFakeTimers();

describe('Tests of inline styles', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

test('Inline styles', () => {
function InlineStyle() {
const width = useSharedValue(100);

const handlePress = () => {
width.value = width.value + 50;
};

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View
testID="view"
style={{
width,
}}
/>
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<InlineStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100 });

fireEvent.press(button);
jest.runAllTimers();

expect(view).toHaveAnimatedStyle({ width: 150 });
});

test('Inline styles with withTiming', () => {
function InlineStyle() {
const width = useSharedValue(100);

const handlePress = () => {
width.value = withTiming(width.value + 50, { duration: 500 });
};

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View
testID="view"
style={{
width,
height: 100,
backgroundColor: 'violet',
}}
/>
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<InlineStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({
width: 100,
height: 100,
backgroundColor: 'violet',
});

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({
width: 150,
height: 100,
backgroundColor: 'violet',
});
});

test('Double inline styles (single object)', () => {
function UseAnimatedStyle() {
const width = useSharedValue(100);
const height = useSharedValue(100);

const handlePress = () => {
width.value = width.value + 50;
height.value = height.value + 50;
};

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View testID="view" style={{ width, height }} />
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<UseAnimatedStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100, height: 100 });

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({ width: 150, height: 150 });
});

test('Double inline styles (array)', () => {
function UseAnimatedStyle() {
const width = useSharedValue(100);
const height = useSharedValue(100);

const handlePress = () => {
width.value = width.value + 50;
height.value = height.value + 50;
};

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View testID="view" style={[{ width }, { height }]} />
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<UseAnimatedStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100, height: 100 });

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({ width: 150, height: 150 });
});

test('Inline & useAnimatedStyle()', () => {
function UseAnimatedStyle() {
const width = useSharedValue(100);
const height = useSharedValue(100);

const handlePress = () => {
width.value = width.value + 50;
height.value = height.value + 50;
};

const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
}, [width]);

return (
<View style={{ flex: 1, alignItems: 'center' }}>
<Animated.View testID="view" style={[animatedStyle, { height }]} />
<Button testID="button" onPress={handlePress} title="Click me" />
</View>
);
}

render(<UseAnimatedStyle />);
const view = screen.getByTestId('view');
const button = screen.getByTestId('button');

expect(getAnimatedStyle(view)).toEqual({ width: 100, height: 100 });

fireEvent.press(button);

jest.runAllTimers();

expect(getAnimatedStyle(view)).toEqual({ width: 150, height: 150 });
});
});
2 changes: 1 addition & 1 deletion packages/react-native-reanimated/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"@react-native/typescript-config": "0.75.1",
"@testing-library/jest-native": "^4.0.4",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/react-native": "^7.1.0",
"@testing-library/react-native": "^12.5.2",
"@types/babel__core": "^7.20.0",
"@types/babel__generator": "^7.6.4",
"@types/babel__traverse": "^7.14.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export interface IAnimatedComponentInternal {
*/
_componentViewTag: number;
_isFirstRender: boolean;
jestInlineStyle: NestedArray<StyleProps> | undefined;
jestAnimatedStyle: { value: StyleProps };
_component: AnimatedComponentRef | HTMLElement | null;
_sharedElementTransition: SharedTransition | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
IAnimatedComponentInternal,
ViewInfo,
INativeEventsManager,
NestedArray,
} from './commonTypes';
import { flattenArray } from './utils';
import setAndForwardRef from './setAndForwardRef';
Expand All @@ -53,6 +54,7 @@ import { NativeEventsManager } from './NativeEventsManager';
import type { ReanimatedHTMLElement } from '../js-reanimated';

const IS_WEB = isWeb();
const IS_JEST = isJest();

if (IS_WEB) {
configureWebLayoutAnimations();
Expand Down Expand Up @@ -121,6 +123,7 @@ export function createAnimatedComponent(
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
_componentViewTag = -1;
_isFirstRender = true;
jestInlineStyle: NestedArray<StyleProps> | undefined;
jestAnimatedStyle: { value: StyleProps } = { value: {} };
_component: AnimatedComponentRef | HTMLElement | null = null;
_sharedElementTransition: SharedTransition | null = null;
Expand All @@ -136,7 +139,7 @@ export function createAnimatedComponent(

constructor(props: AnimatedComponentProps<InitialComponentProps>) {
super(props);
if (isJest()) {
if (IS_JEST) {
this.jestAnimatedStyle = { value: {} };
}
const entering = this.props.entering;
Expand Down Expand Up @@ -361,7 +364,7 @@ export function createAnimatedComponent(
name: viewName,
shadowNodeWrapper,
});
if (isJest()) {
if (IS_JEST) {
/**
* We need to connect Jest's TestObject instance whose contains just props object
* with the updateProps() function where we update the properties of the component.
Expand Down Expand Up @@ -561,7 +564,7 @@ export function createAnimatedComponent(
render() {
const filteredProps = this._PropsFilter.filterNonAnimatedProps(this);

if (isJest()) {
if (IS_JEST) {
filteredProps.jestAnimatedStyle = this.jestAnimatedStyle;
}

Expand Down Expand Up @@ -590,10 +593,18 @@ export function createAnimatedComponent(
const nativeID =
skipEntering || !isFabric() ? undefined : `${this.reanimatedID}`;

const jestProps = IS_JEST
? {
jestInlineStyle: this.props.style,
jestAnimatedStyle: this.jestAnimatedStyle,
}
: {};

return (
<Component
nativeID={nativeID}
{...filteredProps}
{...jestProps}
// Casting is used here, because ref can be null - in that case it cannot be assigned to HTMLElement.
// After spending some time trying to figure out what to do with this problem, we decided to leave it this way
ref={this._setComponentRef as (ref: Component) => void}
Expand Down
Loading

0 comments on commit 00f074f

Please sign in to comment.