I'm fairly new to unit testing my .tsx files and I am currently having trouble testing this (sorry if the format is off)
//this is Banner.tsx
import React, {useCallback} from "react";
type Properties = {
close: () => void;
text: string;
const Banner: React.FC<Properties> = ({close, text}) => {
const onClick = useCallback(() => {
close();},
[close, text]);
return (
<div className = "BannerBox">
<div className = "banner">
<span className = "popup"> onClick={onClick}[x]
</span>
{text}
</div>
</div>
);
};
export default Banner;
//this is App.tsx
import Banner from "./Components/Banner";
function App(): JSX.Element {
const [isOpen, setIsOpen]=useState(false);
const toggleBanner = () => {
SetIsOpen(!isOpen);
};
return (
<div>
<input type = "button"
value = "popup"
onClick={toggleBanner}/>
<p>hi</p>
{isOpen && <Banner text = {"hello"} close={() => isOpen(false)}/>}
</div>
export default App;
this is what i have so far
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner />);
})
it gives this error -> "type {} is missing the following properties from type Banner: close and text"
"type {} is missing the following properties from type Banner: close and text"
Read this error message carefully.
Banner is a functional component. That means it's a function that that takes it's props as an object. And it's typed to receive two props, close and text. These props are required.
But you are providing no props in your test. Since the props argument is always an object, and you have no props, then the props argument is an empty object.
So now that error tells you that your function expects an object, but the one you provided is missing the close and text props.
You need to satisfy the required props of your component. Whether you are in a test or not, the contract of those types must must be fulfilled.
That means you want something like this:
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner text="Hello, World!" close={() => null} />);
})
In additional there several syntax errors in your components. And your code will be much easier to understand if you use proper indenting to inform you of the structure of your code.
Related
I have a functional component called MyDivBlock
const MyDivBlock: FC<BoxProps> = ({ }) => {
{getting data...}
return (
<>
<div className='divBlock'>
{data.map((todo: { id: string; title: string }) =>
<div key={todo.id}>{todo.id} {todo.title} </div>)}
</div>
</>
);
};
I use it in such a way that MyDivBlock is nested as a child of
const App: NextPage = () => {
return (
<div>
<Box >
<MyDivBlock key="key0" areaText="DIV1" another="another"/>
</Box>
</div>
)
}
Note that MyDivBlock is nested in Box and MyDivBlock has no ref attribute. This is important because I need to write Box code with no additional requirements for my nested children. And anyone who will use my Box should not think about constraints and ref attributes.
Then I need to get the dimensions of MyDivBlock in the code of Box component, and later attach some event listeners to it, such as scrolling. These dimensions and listeners will be used in the Box component. I wanted to use Ref to control it. That is, the Box will later observe changes in the dimensions and events of MyDivBlock by creating a ref-reference to them
I know that this kind of parent-child relationship architecture is implemented through forwardRef
And here is the Box code:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
export interface BoxProps extends React.ComponentProps<any> {
children?: Element[];
className: string;
}
export const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const methods = {
show() {
if (childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, function (item) {
console.log(item)})
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
return <div ref={childRef}> {children} </div>
});
ChildWithForwardRef.displayName = 'ChildWithForwardRef';
return (
<div
className={'BoxArea'}>
<button name="ChildComp" onClick={() => childRef.current.show()}>get Width</button>
<ChildWithForwardRef ref={childRef} />
</div>
);
}
export default Box;
The result of pressing the button:
childRef.current is present...
[...]
$$typeof: Symbol(react.element) key: "key0" props: {areaText: 'DIV1', another: 'another'}
[...] Object
offsetWidth = undefined
As you can see from the output, the component is visible through the created ref. I can even make several nested ones and get the same for all of them.
But the problem is that I don't have access to the offsetWidth and other properties.
The other challenge is how can I add the addEventListener?
Because it works in pure Javascript with their objects like Element, Document, Window or any other object that supports events, and I have ReactChildren objects.
Plus I'm using NextJS and TypeScript.
Didn't dive too deep into the problem, but this may be because you are passing the same childRef to both div inside ChildWithForwardRef and to ChildWithForwardRef itself. The latter overwrites the former, so you have the method .show from useImperativeHandle available but not offsetWidth. A quick fix is to rewrite ChildWithForwardRef to use its own ref:
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const ref = useRef<HTMLDivElement>()
const methods = {
show() {
if (ref.current) {
console.log("ref.current is present...");
React.Children.forEach(children, (item) => console.log(item))
console.log("offsetWidth = " + ref.current.offsetWidth);
} else {
console.log("ref.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
// Here ref instead of childRef
return <div ref={ref}> {children} </div>
});
But really I don't quite get why you would need ChildWithForwardRef at all. The code is basically equivalent to this simpler version:
const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const showWidth = () => {
if(childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, item => console.log(item))
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
}
return (
<div className={'BoxArea'}>
<button name="ChildComp" onClick={showWidth}>get Width</button>
<div ref={childRef}>{children}</div>
</div>
);
}
You can't solve this completely with React. I solved it by wrapping the child component, making it take the form of the parent.
I have one React component defined as follows:
const BirthdaySearch: FC<{ onSearch: (year: string, month: string) => void }> =
(props) => {
const monthInputRef = useRef<HTMLInputElement>(null);
const dayInputRef = useRef<HTMLInputElement>(null);
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const selectedDay = dayInputRef!.current!.value;
const selectedMonth = monthInputRef!.current!.value;
props.onSearch(selectedYear, selectedMonth);
};
return (
<form onSubmit={submitHandler}>
<div>
<div>
<label htmlFor="month">Month</label>
<input name="month" id="month" ref={monthInputRef} />
</div>
<div>
<label htmlFor="month">Day</label>
<input name="day" id="day" ref={dayInputRef} />
</div>
</div>
<Button>Find People By Birthday</Button>
</form>
);
};
export default BirthdaySearch;
The onSearch function is typed in the props of the element, but when it's misused in the parent component by mentioning fewer arguments than are defined in the child component, TypeScript doesn't mind:
const Birthdays: NextPage = () => {
const findBdHandler = () => {
// do things...
};
return <BirthdaySearch onSearch={findBdHandler} />
};
export default Birthdays;
TypeScript only complains if the types are wrong or there are more arguments than defined in the child. Is there a way to make this more strict?
The reason that typescript doesn't complain is that the code is perfectly valid: it is perfectly fine and common practice to ignore parameters for a function. For example, onClick handlers often get the event as a parameter, but if you don't care about the event you can just pass in a handler that doesn't take parameters.
Typescript or for that matter any other type checker would not throw an error when you use lesser params than specified (They would show an error if you mention a param as variable and then leave it unused).
If you want to make it strict to check the values then you should type it at the parent level then use the function in the child. This should throw an error when the child does not pass the required params.
Definitions flow from parent to child and not the other way.
Hopefully I've explained it clearly
Here is the structure of my folder:
src--|
|--ComponentsFolder1--Button.tsx
|--ComponentsFolder2--ParentDiv.tsx
My problem is as follows:
Button.tsx is a react component which calls a fetch function located in react ParentDiv.tsx with a few parameters:
type getNewVerseProps = {
getNewVerseFunc: (event: React.MouseEvent<HTMLDivElement>) => string;
};
const Button = ({ getNewVerseFunc }: getNewVerseProps) => {
return (
<div>
<div
onClick={getNewVerseFunc.bind(name, id)}>
</div>
</div>
Now, as you can see, I want to call the function on my ParentDiv.tsx file with the specified parameters in Button.tsx:
const getNewVerseFunc = async (name: string, id: string) => {
const requ = await fetch(
`https://api.scripture.api.bible/${name}/${id}`,
{
method: "GET",
headers: {
"api-key": bibleApi,
},
}
};
<Button getNewVerseFunc={getNewVerseFunc}/>
My problem is that the params in the function are not being passed down in the Button component when i call the getNewVerseFunc function in the ParentDiv component
First of all you've used bind wrongly. First argument is a context, which is in this case ignored, because you have an arrow function, which is instantly bound; but it is impossible to rebind bound function.
Common practice is to have button with onClick callback, like:
import { FunctionComponent } from 'react';
type Props {
onClick: MouseEventHandler;
}
const Button: FunctionComponent<Props> = ({ onClick, children }) => (
<div onClick={getNewVerseFunc.bind(name, id)}>
{children}
</div>
);
And for simplicity avoid using bind, call or apply if possible; it's good that you are aware of these functions, but using them usually reduces readability.
Then in parent you can do the following:
import { FunctionComponent } from 'react';
const ParentDiv: FunctionComponent = () => {
const name = 'Some name';
const id = 'Some id';
return (
<Button onClick={() => getNewVerseFunc(name, id)}>
Click me
</Button>
);
};
If your Button component is wrapped with memo, then it makes sense to use useCallback hook for onClick handler, but that's already micro optimisation.
I have a Button component that looks like this:
const Button = ({
children,
type,
btnStyles,
onClick,
}: any) => {
//...
}
export default Button
I wan't to make sure that the button is only used with the allowed props defined above. My test currently looks like this:
it('should only have the allowed props', () => {
const tree = shallow(
<Button
type='button'
buttonStyles='a-custom-button'
onClick={() => {
console.log('Custom Button Clicked')
}}
>
<i></i>
Button
</Button>
)
expect(tree.prop('type')).toBeDefined()
expect(tree.prop('buttonStyles')).toBeDefined()
})
I also want to make sure that only allowed class names can be used for buttonStyles. For example the button can only contain the classes .a-custom-button and .a-custom-button-styles.
How can I do this with Jest?
#Clarity is correct. You're using TypeScript so you could use a union of valid class names:
type ButtonProps = {
btnStyles: 'a-custom-button' | 'a-custom-button-styles';
}
const Button = ({
btnStyles,
}: ButtonProps) => {
console.log(btnStyles);
return null;
}
Button({ btnStyles: 'a-custom-button' }) // works
Button({ btnStyles: 'a-custom-button-styles' }) // also works
Button({ btnStyles: 'invalid-class-name' }) // doesn't work - invalid prop value in TypeScript
Playground:
https://www.typescriptlang.org/play?#code/C4TwDgpgBAQgrsYB7AdgBQE5LAZygXigG8AoKKAI2BQGVQAbCHALigHIBDAWgGM4dkAWy4UEyFGygAfdtz4Ckw0YlRcBIRjjYBuEgF8SJHqgGwxqAlAAUpcuSq0GTADT7W8Feiy4AlAQB8xGTkxig4SIwAdPRIAOZWDnQaTD66dhgQwHAYKFAocPT0ugYkHuI2lNRJmqycvPxCIuYSUHp+dh0A9J1QAO5IGADWOKXNFYlOLLL1CkrNapOSbVDdUBz04X0Dw6Oe41WTtQCWKABu60cAJrz0HDg4XCgcghBL7XarJ+f0V1Bg3lBvnBoAAVcAQGg8DBHMDAKAQDBYDAkIA
I was trying to render a DOM element with a prop but my attempt is not recognized by React as a legal DOM attribute/property.
So I started researching for a solution and I found this source from React Warning Documentation. However, although well explained, I have a deeper problem that will involve a deeper solution therefore.
As an example of code I will introduce the ExampleContainer method:
export let ExampleContainer = (props) => {
return (
<DetailsContainer tabs={props.tabs} activeTab={props.activeTab}>
{props.children}
</DetailsContainer>
)
}
Now I have my mapStateToProps receiving a statethat I beleave is well implemented and I will bring it here just to context my problem:
const mapStateToProps = (state) => {
return {
i18n: state.i18n,
activeTab : state.example.activeTab
}
}
Finally, the problem is inside my mergeProps where I have this tabs source giving me the problem and more specificly inside it on the i18n attribute:
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, {
tabs: [
{
'onClick' : dispatchProps.goToExampleOptionA,
'key' : 'ExampleOptionA',
'i18n' : stateProps.i18n.Tabs.exampleOptionATab
},
{
'onClick' : dispatchProps.goToExampleOptionB,
'key' : 'ExampleOptionB',
'i18n' : stateProps.i18n.Tabs.exampleOptionBTab
}
]
}, stateProps)
}
The problem is when I open my Container it brings to me this warning:
And my DeyailsContainer component is this one:
let DetailsContainer = ({
tabs,
rightIcons,
activeTab,
children
}) => {
return (
<div>
<Tabs tabs={tabs} rightIcons={rightIcons} activeTab={activeTab}/>
<div className="app-wrapper">
{children}
</div>
</div>
)
}
This is due to passing your i18n prop directly into a <div i18n="whatever" /> somewhere.
My guess is it's inside your DetailsContainer component. If you're spreading all of the props your component receives into the div like this <div {...props} />, that will definitely do it.
React has warnings now when you try to pass some type of prop that isn't a standard attribute for a DOM element. If you need the i18n as an attribute for a reason like this... HTML tags in i18next translation, you'll want to change that to data-i18n as the prop.