React testing library fireEvent not firing / found answer - reactjs

I have an issue with using react testing library and its fireEvent function.
I want to test my component and its style after hovering.
Here it is my component written in tsx:
import React from 'react'
import { makeStyles } from '#material-ui/core'
import Paper, { PaperProps } from '#material-ui/core/Paper'
import Box from '#material-ui/core/Box'
import posed from 'react-pose'
import grey from '#material-ui/core/colors/grey'
import green from '#material-ui/core/colors/green'
import lightBlue from '#material-ui/core/colors/lightBlue'
import teal from '#material-ui/core/colors/teal'
function styleProducer(variant: BigTileProps['variant'] = 'default', color: BigTileProps['color'] = 'default'): Function {
type colrMapType = {
[k: string]: { tile: { [pr: string]: string } }
}
const colorMap: colrMapType = {
default: { tile: {} },
grey: {
tile: { background: grey[700], color: 'white' }
},
green: {
tile: { background: green[300] }
},
teal: {
tile: { background: teal[400], color: 'white' }
},
blue: {
tile: { background: lightBlue[200] }
},
}
interface variantsKeys {
small: (theme: any) => object
default: (theme: any) => object
big: (theme: any) => object
large: (theme: any) => object
}
type variantsType = {
[k in keyof variantsKeys]: variantsKeys[k]
}
const variants: variantsType = {
small: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(10),
height: theme.spacing(10),
background: grey[500],
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
default: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(15),
height: theme.spacing(15),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
big: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(20),
height: theme.spacing(20),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
large: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(25),
height: theme.spacing(25),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
})
}
return makeStyles(variants[variant])
}
type BigTileProps = {
color?: 'default' | 'grey' | 'green' | 'blue' | 'teal',
variant?: 'small' | 'default' | 'big' | 'large',
width?: string, // 15px , 10rem etc
height?: string, // 15px , 10rem etc
title?: string,
content?: string
icon?: React.FC | React.ReactElement
}
const PosedPaper = posed(Paper)({
onHover: {
scale: 1.05
},
none: {
scale: 1
}
})
const BigTile: React.FC<BigTileProps> = ({
variant = 'default',
color = 'default',
width,
height,
children,
title,
content,
icon,
...props
}) => {
const [hover, setHover] = React.useState(false)
const useStyles = styleProducer(variant, color)
const classes = useStyles()
const onHoverHandle = (bool: boolean) => () => {
setHover(bool)
}
const style = {height, width}
if (!height) delete style['height']
if (!width) delete style['width']
return (
<PosedPaper className={classes.tile} style={{ height, width }} pose={hover ? 'onHover' : 'none'} onMouseEnter={onHoverHandle(true)} onMouseLeave={onHoverHandle(false)}>
<Box className={classes.icon} p={1}>
{icon}
</Box>
<Box height="100%" width="100%" display="flex" justifyContent="center" alignItems="center" flexDirection="column">
<Box mb={1} className={classes.content}>{children ? children : content}</Box>
<div className={classes.title}>{title}</div>
</Box>
</PosedPaper>
)
}
export default BigTile
And here is my test :
it('BigTile hover check', ()=>{
const { container } = render(<BigTile />)
const elem = container.querySelector<HTMLElement>('.MuiPaper-root')
if (!elem) fail("Element is null.")
fireEvent.mouseOver(elem);
const elemAfterHover = container.querySelector<HTMLElement>('.MuiPaper-root')
if (!elemAfterHover) fail("Element after hover is null.")
console.log(elemAfterHover.style)
})
Here i cant see changed style after mouseover event. Console log shows me only transform:none
when it should be something like transform: scale(1.05).
Please help me with firing this event properly and if you have some advise to the code itself it would be great to take some advices.

//Answer
The event was firing every time i started the test.
To check it i just put some console.log(..) inside onHoverHandle which is firing after moseenter/leave.
Of course i didn't need to find element once again after fireEvent, becouse i had this element before. Main idea is that i needed to wait for event to end before checking for style change. So waitFor is good for that.
I read somewhere than using userEvent from '#testing-library/user-event' is better than fireEvent , so i did.
Final solution :
it('BigTile hover check.', async ()=>{
const { container } = render(<BigTile />)
const elem = container.firstChild as HTMLElement
if (!elem) fail("Element is null.")
userEvent.hover(elem);
await waitFor(() => {
expect(elem.style.transform).toEqual("scale(1.05) translateZ(0)")
})
})

Related

How to extends classes in material UI createStyles

import { createStyles, Theme } from '#material-ui/core/styles';
export default (theme: Theme) => {
const { primary } = theme.palette;
return createStyles({
test1: {
fontSize: '30px',
},
test2: {
'& > div': {
color: primary.main,
marginTop: '20px',
},
},
});
};
Here I want to use class test1 in test2. How should I extend it?
You can use the dollar sign to reference the other selector when using JSS:
test1: {
width: 50,
height: 50,
backgroundColor: "pink",
"& $test2": {
backgroundColor: "red"
}
},
test2: {
width: 30,
height: 30
}
Reference
https://cssinjs.org/jss-plugin-nested?v=v10.8.1#use-rulename-to-reference-a-local-rule-within-the-same-style-sheet

Change style when rotation the smartphone

I use the react-native-orientation-locker package and I would like to change the style when I rotate the smartphone
I use the state to initialize my style, but this solution I don't think is optimal
here is an example of my code
const [styles, setStyles] = useState(Styles(theme, layout))
const changeStyles = (o: OrientationType): void => {
layout = getDeviceVideoLayout(o) // return 'PORTRAIT or 'LANDSCAPE-LEFT' or 'LANDSCAPE-RIGHT'
// change the rotation of the phone
const { height, width } = Dimensions.get('window')
//I update width and height
theme.layout.width = width
theme.layout.height = height
// I update the status
setStyles(Styles(theme, layout))
}
useDeviceOrientationChange((o) => {
changeStyles(o)
})
I don't like this solution of the style in the state
an example of my style
const StyleCommon = (theme: Theme, bottomSpace: number): StyleGeneral => StyleSheet.create({
container: {
height: theme.layout.height,
width: '100%',
backgroundColor: theme.colors.black
},
actionButtonContainer: {
width: 50,
backgroundColor: 'rgba(242, 241, 240, 0.3)',
borderRadius: 12,
marginBottom: 20
}
})
const StyleLayoutPortrait = (theme: Theme, bottomSpace: number): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
bottom: theme.spacing.sm + theme.layout.insets.bottom + 40 + bottomSpace,
right: theme.spacing.sm
}
})
const StyleLayoutLandscapeLeft = (theme: Theme): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
top: theme.spacing.sm,
right: theme.spacing.sm
}
})
const StyleLayoutLandscapeRight = (theme: Theme): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
right: theme.spacing.sm + theme.layout.insets.top,
top: theme.spacing.sm
}
})
const Styles = (theme: Theme, layout: VideoLayoutType, bottomSpace: number): Style => {
if (layout === 'LANDSCAPE-RIGHT') {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutLandscapeRight(theme)
})
} else if (layout === 'LANDSCAPE-LEFT') {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutLandscapeLeft(theme)
})
} else {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutPortrait(theme, bottomSpace)
})
}
}
I do not think this solution is optimal, how could I do?
indicatorContainerProps:{
position: 'fixed',
right: '0px',
bottom: '0%',
marginBottom: '40%',
display:'flex',
justifyContent:'center',
flexDirection:'row',
alignItems:'center',
"#media (orientation:landscape)": {
marginBottom: '0px'
}
This example is for Jsx or ReactJs.
You just need to specify the some class name like text
text:{
position: 'fixed',
right: '0px',
bottom: '0%',
marginBottom: '40%',
display:'flex',
justifyContent:'center',
flexDirection:'row',
alignItems:'center',
"#media (orientation:landscape)": { //here you can change based on orientation
marginBottom: '0px'
}

Type 'undefined' cannot be used as an index type

Im getting this error trying to pass the value of a Map into a theme prop dynamically. what i do wrong?
ps: can't add "!" cause of eslint
interface ColorsInterface {
bgColor: keyof ThemeVars['colors'];
textColor: keyof ThemeVars['colors'];
}
const colorMap = new Map<string, ColorsInterface>([
['standard', { bgColor: 'primaryNormal', textColor: 'white' }],
['accent', { bgColor: 'accentNormal', textColor: 'white' }],
['neutral', { bgColor: 'grayExtraDark', textColor: 'white' }],
['flat', { bgColor: 'white', textColor: 'primaryNormal' }],
['text', { bgColor: 'white', textColor: 'grayExtraDark' }],
['disabled', { bgColor: 'grayLight', textColor: 'grayDark' }],
]);
export const ButtonWrapper = styled.button<ButtonInterface>`
${props =>
props.buttonType &&
css`
background-color: ${props.theme.colors[colorMap.get(props.buttonType).bgColor]};
color: ${colorMap.get(props.buttonType)?.textColor};
`}
`;
I get the error while passing the color to the theme in the background-color
Unfortunately, you cannot force Map to have non-undefined return type. So you need to replace it with a simple object and assign a Record type to it.
Also I would recommend you to create a type union ColorMapKeys where you should define all possible keys.
type ColorMapKeys = 'standard' | 'accent' | 'neutral' | 'flat' | 'text' | 'disabled';
type ColorMap = Record<ColorMapKeys, ColorsInterface>;
const colorMap: ColorMap = {
'standard': { bgColor: 'primaryNormal', textColor: 'white' },
'accent': { bgColor: 'accentNormal', textColor: 'white' },
'neutral': { bgColor: 'grayExtraDark', textColor: 'white' },
'flat': { bgColor: 'white', textColor: 'primaryNormal' },
'text': { bgColor: 'white', textColor: 'grayExtraDark' },
'disabled': { bgColor: 'grayLight', textColor: 'grayDark' },
};
export const ButtonWrapper = styled.button<ButtonInterface>`
${(props: Props) =>
props.buttonType &&
css`
background-color: ${props.theme.colors[colorMap[props.buttonType].bgColor]};
color: ${colorMap[props.buttonType].textColor};
`}
`;
Now everything will work fine

React Material UI TextField Styles Not Working

I'm trying to style the TextField API provided by Material UI (found here), however, for some reason, the styling is not being applied to the component. When I render it on a webpage, it's shown as in its default form. I would greatly appreciate any help on this. Here is my code.
interface CustomEmptyFieldProps {
usernameOrPass: string,
}
const baseMuiOutlineInputSizeAndPosition = {
borderRadius: 8,
width: '328px',
height: '40px',
};
const baseTextFieldSizeAndPosition = () => (
{
"& label:not(.Mui-focused)": { // Label in center of TextInput
marginTop: '-8px',
},
"& label:.Mui-shrink": { // Label in center of TextInput
marginTop:'-8px',
},
width: '328px',
height: '40px'
}
);
const useTextFieldStyles = (isTypedIn: boolean) => (
makeStyles({
"& label.Mui-focused, label:not(.Mui-focused)": { color: TextFieldColours.error.label },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: TextFieldColours.error.border, },
"&:hover fieldset": { borderColor: TextFieldColours.error.border, },
"&.Mui-focused fieldset": { borderColor: TextFieldColours.error.border },
...baseMuiOutlineInputSizeAndPosition,
},
...baseTextFieldSizeAndPosition,
})
);
const EmptyTextField = (props: CustomEmptyFieldProps) => {
const {
usernameOrPass,
} = props;
const inputLabel = "VolunteerHub " + usernameOrPass;
const errorMessage = "Please enter your VolunteerHub " + usernameOrPass;
const textFieldStyles = useTextFieldStyles(false);
return (
<div>
<TextField
classes={{ root: textFieldStyles, }}
placeholder={inputLabel}
id="outlined-error-helper-text"
defaultValue=""
helperText={errorMessage}
variant="outlined"
/>
</div >
);
}
Not sure about the way you declare your useTextFieldStyles. Here is how I would usually do:
const useTextFieldStyles = makeStyles(() => ({
root: {
"& label.Mui-focused, label:not(.Mui-focused)": {
color: TextFieldColours.error.label
},
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: TextFieldColours.error.border },
"&:hover fieldset": { borderColor: TextFieldColours.error.border },
"&.Mui-focused fieldset": {
borderColor: TextFieldColours.error.border
},
...baseMuiOutlineInputSizeAndPosition
},
...baseTextFieldSizeAndPosition
}
}));
Working sample: https://codesandbox.io/s/runtime-sky-x14vr?file=/src/App.tsx:647-1173

How to put the result of an API request into array value in react native?

I have a problem to put the result of an API request into an array value in my react native app.
this is the called function
export function getNewFilmFromApi(page) {
return fetch(
'https://api.themoviedb.org/3/discover/movie?api_key=' +
API_TOKEN +
'&release_date.gte=2019-10-01&release_date.lte=2019-12-31&language=fr&page=' +
page,
)
.then(response => response.json())
.catch(error => console.error(error));
}
and this is how I'm trying to put the returned value into the array :
const projects = [
{
title: getNewFilmFromApi().then(data => {
return data.results[0].title;
}),
image: require('../Images/background.jpg'),
author: 'Liu Yi',
text: 'kkkk',
},
{
title: 'The DM App - Ananoumous Chat',
image: require('../Images/background.jpg'),
author: 'Chad Goodman',
text: 'kjhgfhjkl',
},
{
title: 'Nikhiljay',
image: require('../Images/background.jpg'),
author: "Nikhil D'Souza",
text: 'jjjjjj',
},
];
I can see with console.log that there's a value for data.results[0].title but I can't put it in the array!
This is the error I have when I try to do this :
this is my render function and everything work except the title which I want to return it from API.
render() {
return (
<Container>
<AnimatedMask style={{opacity: this.state.opacity}} />
<Animated.View
style={{
transform: [
{translateX: this.state.pan.x},
{translateY: this.state.pan.y},
],
}}
{...this._panResponder.panHandlers}>
<Project
title={projects[this.state.index].title}
image={projects[this.state.index].image}
author={projects[this.state.index].author}
text={projects[this.state.index].text}
canOpen={true}
/>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
top: 230,
left: 0,
zIndex: -1,
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
transform: [
{scale: this.state.scale},
{translateY: this.state.translateY},
],
}}>
<Project
title={projects[getNextIndex(this.state.index)].title}
image={projects[getNextIndex(this.state.index)].image}
author={projects[getNextIndex(this.state.index)].author}
text={projects[getNextIndex(this.state.index)].text}
/>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
top: 240,
left: 0,
zIndex: -2,
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
transform: [
{scale: this.state.thirdScale},
{translateY: this.state.thridTranslateY},
],
}}>
<Project
title={projects[getNextIndex(this.state.index + 1)].title}
image={projects[getNextIndex(this.state.index + 1)].image}
author={projects[getNextIndex(this.state.index + 1)].author}
text={projects[getNextIndex(this.state.index + 1)].text}
/>
</Animated.View>
</Container>
);
}
}
export default connect(mapStateToProps)(Card);
const Mask = styled.View`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.25);
z-index: -3;
`;
const AnimatedMask = Animated.createAnimatedComponent(Mask);
const Container = styled.View`
flex: 1;
justify-content: center;
align-items: center;
background: #f0f3f5;
margin-top: 80;
`;
const Text = styled.Text``;
var test;
const projects = [
{
title: getNewFilmFromApi().then(data => {
return data.results[0].title;
}),
image: require('../Images/background.jpg'),
author: 'Liu Yi',
text: 'kkkk',
},
{
title: 'The DM App - Ananoumous Chat',
image: require('../Images/background.jpg'),
author: 'Chad Goodman',
text: 'kjhgfhjkl',
},
{
title: 'Nikhiljay',
image: require('../Images/background.jpg'),
author: "Nikhil D'Souza",
text: 'jjjjjj',
},
];
Can you give any solution to display the value into the array please ?
thank you !
The way you're trying to populate your array with results from an API call will not work.
Your API call is asynchronous and it returns a Promise. Therefore you have to wait for your API call to finish and for the Promise to resolve, then update your projects array when your component renders.
I suggest you store your projects array in a state and make an API call when your component renders for the first time, like in the following simplified example:
function getNewFilmFromApi(page) {
return fetch(`https://jsonplaceholder.typicode.com/todos/1`).then(res =>
res.json()
);
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
fetchError: false,
isLoading: true,
projects: [
{ title: null, author: `Liu Yi` },
{ title: `The DM App - Anonymous Chat`, author: `Chad Goodman` },
{ title: `Nikhiljay`, author: `Nikhil D'Souza` }
]
};
}
componentDidMount() {
const handleResponse = data => {
const { title } = data;
// assuming you just want to update
// the first item in the `projects` array
const nextProjects = [...this.state.projects];
nextProjects[0].title = title;
this.setState({
projects: nextProjects,
isLoading: false
});
};
const handleError = err => {
this.setState({
apiError: true,
isLoading: false
});
};
getNewFilmFromApi()
.then(handleResponse)
.catch(handleError);
}
render() {
const { apiError, isLoading, projects } = this.state;
if (isLoading) {
return <p>Loading...</p>;
}
if (!isLoading && apiError) {
return <p>Error loading projects</p>;
}
return <p>{JSON.stringify(projects)}</p>;
}
}
Here's a link to a working example:
CodeSandbox

Resources