import FontistiIcon from '@expo/vector-icons/Fontisto';
import React, { useCallback, useMemo } from 'react';
import { LayoutRectangle, View } from 'react-native';
import {
Gesture,
GestureDetector,
TouchableOpacity,
} from 'react-native-gesture-handler';
import { useTheme } from '@theme';
import { roundToStep } from '@utils';
import { useStyle } from './numerical-rating.styles';
import { RatingProps } from './numerical-rating.type';
const NumericalRating: React.FC = props => {
const {
scale = 5,
testID = 'TestID__component-NumericalRating',
starTestID = 'TestID__component-NumericalRating-star',
isFractional = false,
onChange,
value,
} = props;
const styles = useStyle();
const theme = useTheme();
const [ratingContainerLayout, setRatingContainerLayout] =
React.useState(null);
// if scale is not a whole number, then we need to round it to the nearest whole number
const scaleRounded = Math.round(scale);
const scaleList = Array.from(Array(scaleRounded).keys(), x => x + 1);
// Create [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
const fractionalScaleList = scaleList.reduce(
(acc, curr) => [...acc, [curr - 0.5, curr]],
[],
);
const iconSize = theme.t.moderateScale(58);
const iconWidth = Math.floor(iconSize / 2);
// value should be between the range of 0 and scale
const selectedRating = Math.max(0, Math.min(value, scale));
const [draggedRating, setDraggedRating] = React.useState(selectedRating);
const onRatingTap = (rating: number) => () => {
onChange(rating);
setDraggedRating(rating);
};
const getRatingByPosition = useCallback(
(position: number) => {
if (ratingContainerLayout?.width) {
// get the rating icon size based on the number of stars and width of the container
const ratingIconSize = ratingContainerLayout.width / scaleRounded;
// Calculate the rating value based on the position of the finger
const calculatedRatingValue = position / ratingIconSize;
return roundToStep(calculatedRatingValue, isFractional ? 0.5 : 1);
}
return 0;
},
[isFractional, ratingContainerLayout, scaleRounded],
);
/**
* ONLY IF YOU NEED IT
* 1. shouldCancelWhenOutside - should cancel the gesture if the user moves their
* finger outside of the rating container
*
* 2. failOffsetY([0, 0]) - should fail the gesture if the user moves their finger
* vertically. First zero indicates the minimum offset to the top, second zero
* indicates the minimum offset to the bottom. We have set both to zero because
* we want the user to be able to move their finger vertically so that they could
* scroll the screen
*/
const gesture = useMemo(
() =>
Gesture.Pan()
.runOnJS(true)
.onUpdate(e => {
setDraggedRating(getRatingByPosition(e.x));
})
.onEnd(e => {
onChange(getRatingByPosition(e.x));
}),
[getRatingByPosition, onChange],
);
// Render the full icon rating
const renderScale = () =>
scaleList.map(rating => (
= rating,
}}
>
= rating
? theme.t.palette.accents.color2
: theme.t.palette.accents.color4
}
/>
));
// fractionalScaleList = e.g [[0.5, 1], [1.5, 2], [2.5, 3], [3.5, 4], [4.5, 5]]
// Render the fractional icon rating
const renderFractionalScale = () =>
fractionalScaleList.map((ratingPair: number[], index) => (
{/* ratingPair = e.g [0.5, 1] */}
{ratingPair.map((rating: number) => (
= rating,
}}
>
= rating
? theme.t.palette.accents.color2
: theme.t.palette.accents.color4
}
/>
))}
));
// Render the rating scale based on the isFractional prop
const renderRating = () => {
if (isFractional) {
return renderFractionalScale();
}
return renderScale();
};
return (
{/* This View helps us in letting the panning continue even after
reaching the end */}
{/* This View is contained within the width of the rendered stars */}
setRatingContainerLayout(e.nativeEvent.layout)}
>
{renderRating()}
);
};
export default NumericalRating;
Я пытаюсь протестировать жест панорамирования с помощью fireGestureHandler из реакции-native-gesture-handler/jest-utils. Ниже вы можете увидеть написанный мною код:
Компонент не распознает жест панорамирования. В реакции-native-gesture-handler не так уж много примеров, показывающих, как мы тестируем жесты панорамирования. Часть кода, связанная с жестом панорамирования, уменьшает охват моего кода.
Я создал компонент рейтинга, который можно перелистывать: [code]import FontistiIcon from '@expo/vector-icons/Fontisto'; import React, { useCallback, useMemo } from 'react'; import { LayoutRectangle, View } from 'react-native'; import { Gesture, GestureDetector, TouchableOpacity, } from 'react-native-gesture-handler';
import { useTheme } from '@theme'; import { roundToStep } from '@utils';
import { useStyle } from './numerical-rating.styles'; import { RatingProps } from './numerical-rating.type';
// if scale is not a whole number, then we need to round it to the nearest whole number const scaleRounded = Math.round(scale); const scaleList = Array.from(Array(scaleRounded).keys(), x => x + 1);
// value should be between the range of 0 and scale const selectedRating = Math.max(0, Math.min(value, scale)); const [draggedRating, setDraggedRating] = React.useState(selectedRating);
const getRatingByPosition = useCallback( (position: number) => { if (ratingContainerLayout?.width) { // get the rating icon size based on the number of stars and width of the container const ratingIconSize = ratingContainerLayout.width / scaleRounded;
// Calculate the rating value based on the position of the finger const calculatedRatingValue = position / ratingIconSize;
/** * ONLY IF YOU NEED IT * 1. shouldCancelWhenOutside - should cancel the gesture if the user moves their * finger outside of the rating container * * 2. failOffsetY([0, 0]) - should fail the gesture if the user moves their finger * vertically. First zero indicates the minimum offset to the top, second zero * indicates the minimum offset to the bottom. We have set both to zero because * we want the user to be able to move their finger vertically so that they could * scroll the screen */ const gesture = useMemo( () => Gesture.Pan() .runOnJS(true) .onUpdate(e => { setDraggedRating(getRatingByPosition(e.x)); }) .onEnd(e => { onChange(getRatingByPosition(e.x)); }), [getRatingByPosition, onChange], );
// Render the full icon rating const renderScale = () => scaleList.map(rating => ( = rating, }} >
// Render the rating scale based on the isFractional prop const renderRating = () => { if (isFractional) { return renderFractionalScale(); }
return renderScale(); };
return (
{/* This View helps us in letting the panning continue even after reaching the end */}
{/* This View is contained within the width of the rendered stars */} setRatingContainerLayout(e.nativeEvent.layout)} > {renderRating()}
); };
export default NumericalRating;
[/code] Я пытаюсь протестировать жест панорамирования с помощью fireGestureHandler из реакции-native-gesture-handler/jest-utils. Ниже вы можете увидеть написанный мною код: [code] it('should select the correct star on swipe', () => { const onChange = jest.fn(); render();
expect(onChange).toHaveBeenCalledWith(1); }); [/code] Компонент не распознает жест панорамирования. В реакции-native-gesture-handler не так уж много примеров, показывающих, как мы тестируем жесты панорамирования. Часть кода, связанная с жестом панорамирования, уменьшает охват моего кода.