How to write a unit test to cover custom PropTypes in reactjs? - reactjs

I have a reactjs component with a custom properties, that actually represents just a list of allowed extensions.
For example, my component can receive as a prop something like this:
<CustomComponent allowedTypes={['.pdf', '.txt']} />
and the prop types are defined like this
CustomComponent.propTypes = {
allowedTypes: PropTypes.arrayOf(
(propValue, key, componentName, location, propFullName) => {
if (!new RegExp(/^\.[^.]+$/).test(propValue[key])) {
return new Error(
`Invalid prop ${propFullName} for component ${componentName}.`
)
}
}
)
I need to full cover the component with unit test, so also the code in the prop type definition must be covered.
I tried something similar, but it doesn't work
beforeEach(() => {
console.error = jest.fn()
});
it('should check wrong allowed types', () => {
const wrongAllowedTypes = false
try {
const component = new CustomComponent(Object.assign(defaultProps, { allowedTypes: wrongAllowedTypes } ))
} catch (error) {
expect(console.error).toBeCalled()
}
})
Any ideas, thanks in advance

I suggest pulling out the fat arrow function and giving it a name. Then you can call it directly from a test.

Related

React createProtal called outsite a JSX component not updating the DOM

I am trying to render a dynamically generated react component in a react app using createProtal.
When I call createProtal from a class the component is not rendered.
Handler.ts the class the contains the business logic
export class Handler {
private element: HTMLElement | null;
constructor(selector: string) {
this.element = document.getElementById(selector);
}
attachedEvent() {
this.element?.addEventListener("mouseenter", () => {
let cancel = setTimeout(() => {
if (this.element != null)
this.attachUi(this.element)
}, 1000)
this.element?.addEventListener('mouseleave', () => {
clearTimeout(cancel)
})
})
}
attachUi(domNode: HTMLElement) {
createPortal(createElement(
'h1',
{className: 'greeting'},
'Hello'
), domNode);
}
}
Main.tsx the react component that uses Handler.ts
const handler = new Handler("test_comp");
export default function Main() {
useEffect(() => {
// #ts-ignore
handler.useAddEventListeners();
});
return (
<>
<div id="test_comp">
<p>Detect Mouse</p>
</div>
</>
)
}
However when I repleace attachUi function with the function below it works
attachUi(domNode: HTMLElement) {
const root = createRoot(domNode);
root.render(createElement(
'h1',
{className: 'greeting'},
'Hello'
));
}
What am I missing?
React uses something called Virtual DOM. Only components that are included in that VDOM are displayed to the screen. A component returns something that React understands and includes to the VDOM.
createPortal(...) returns exactly the same as <SomeComponent ... />
So if you just do: const something = <SomeComponent /> and you don't use that variable anywhere, you can not display it. The same is with createPortal. const something = createPortal(...). Just use that variable somewhere if you want to display it. Add it to VDOM, let some of your components return it.
Your structure is
App
-children
-grand children
-children2
And your portal is somewhere else, that is not attached to that VDOM. You have to include it there, if you want to be displayed.
In your next example using root.render you create new VDOM. It is separated from your main one. This is why it is displayed

React how to test props passed to component passed as prop

Okay, this might sound complicated but it will be easy if you read the following example. The main purpose of this is to separate the logic from the actual render code. Making the component smaller and (in theory) easier to test.
class NameProvider {
public getName(): Promise<string> {
return Promise.resolve("Cool name");
}
}
interface RenderProps {
name: string;
onGetNamePress(): void;
}
interface LogicProps {
nameProvider: NameProvider;
render: React.ComponentType<RenderProps>
}
function Render({name, onGetNamePress}: RenderProps): React.ReactElement {
return <>
<p>{name}</p>
<button title="Get name!" onClick={onGetNamePress} />
</>
}
function Logic({nameProvider, render: Render}: LogicProps): React.ReactElement {
const [name, setName] = React.useState<string>();
return <Render
name={name}
onGetNamePress={fetch}
/>
async function fetch() {
setName(await nameProvider.getName());
}
}
Testing the render component is rather easy, but how do I test that the props passed to the render component are correct? Especially after the state changed.
Consider the following:
it('fetches the name after the button was pressed', () => {
const mnp = new MockNameProvider();
render(<Logic
nameProvider={mnp}
render={({name, onGetNamePress}) => {
act(async () => {
await onGetNamePress();
expect(name).toBe(mockName);
})
}}
/>)
})
This will cause an infinite loop, as the state keeps getting changed and the name fetched. I also couldn't imagine how to get the new props. This current code will test the old ones to my understanding. So my question is, how do I test if the props are correctly passed (also after updates).
(Important) Notes:
I'm actually writing a react native app, so maybe the issue is specific to native testing but I didn't think so.
This is not code from our codebase and just cobbled together. Thus also the React prefix, vscode just liked that better in an unsaved file.

JSX Element does not have any construct or call signatures

I am trying to add Application Insights in my ReactJS Application. I changed the JS code that is provided on the GitHub Demo to TypeScript.. now I have
class TelemetryProvider extends Component<any, any> {
state = {
initialized: false
};
componentDidMount() {
const { history } = this.props;
const { initialized } = this.state;
const AppInsightsInstrumentationKey = this.props.instrumentationKey;
if (!Boolean(initialized) && Boolean(AppInsightsInstrumentationKey) && Boolean(history)) {
ai.initialize(AppInsightsInstrumentationKey, history);
this.setState({ initialized: true });
}
this.props.after();
}
render() {
const { children } = this.props;
return (
<Fragment>
{children}
</Fragment>
);
}
}
export default withRouter(withAITracking(ai.reactPlugin, TelemetryProvider));
But when I try to import the same component <TelemetryProvider instrumentationKey="INSTRUMENTATION_KEY" after={() => { appInsights = getAppInsights() }}></Telemetry> I get an error Severity Code Description Project File Line Suppression State
(TS) JSX element type 'TelemetryProvider' does not have any construct or call signatures.
I attempted to simply // #ts-ignore, that did not work. How do I go about solving this?
Given the example above, I hit the same issue. I added the following:
let appInsights:any = getAppInsights();
<TelemetryProvider instrumentationKey={yourkeyher} after={() => { appInsights = getAppInsights() }}>after={() => { appInsights = getAppInsights() }}>
Which seem to solve the issue for me, I am now seeing results in Application Insights as expected.
I guess if you want to have the triggers etc on a different Page/Component you may wish to wrap it in your own useHook or just add something like this to the component.
let appInsights:any;
useEffect(() => {
appInsights = getAppInsights();
}, [getAppInsights])
function trackEvent() {
appInsights.trackEvent({ name: 'React - Home Page some event' });
}
Not the best answer, but it's moved me forward. Would be nice to see a simple hooks version in typescript.
Really hope it helps someone or if they have a cleaner answer.

Passing more values under same prop name and test it with Jest

I am using Jest and Enzyme to test my React-Mobx app. I have come to a problem where I am accessing different values under the same prop name. As you can see I am accessing different properties under the same FilterssAction prop name.
#inject('RootStore', 'FiltersAction')
#observer
class ExpenseListFilters extends Component {
state = {
calendarFocused: null
}
onDatesChange = ({startDate, endDate}) => {
this.props.FiltersAction.setStartDate(startDate)
this.props.FiltersAction.setEndDate(endDate)
}
onTextChange = (e) => {
this.props.FiltersAction.setTextFilter(e.target.value)
}
...
When I write my test it fails. I need to pass props to shallow rendered component. So I am passing different values under same. This is not working, I keep getting an error TypeError: _this.props.FiltersAction.setTextFilter is not a function
How do I test this?
let setStartDateSpy, setEndDateSpy, setTextFilterSpy, sortByDateSpy, sortByAmountSpy, FiltersStore, wrapper
beforeEach(() => {
setStartDateSpy = {setStartDate: jest.fn()}
setEndDateSpy = {setEndDate: jest.fn()}
setTextFilterSpy = {setTextFilter: jest.fn()}
FiltersStore = {FiltersStore: {filters: filters}}
wrapper = shallow(
<ExpenseListFilters.wrappedComponent
FiltersAction = {
setStartDateSpy,
setEndDateSpy,
setTextFilterSpy
}
RootStore = {FiltersStore}
/>
)
})
test('should handle text change', () => {
const value = 'rent'
wrapper.find('input').at(0).simulate('change', {
target: {value}
})
expect(setTextFilterSpy).toHaveBeenLastCalledWith(value)
})
It looks like your component is expecting FiltersAction to be an object whose values are functions.
In your application code, you can see that it as accessing the FiltersActions prop with dot-notation, and executing it as a function. For example, this.props.FiltersAction.setEndDate(endDate)
To pass a prop as an object, you need to wrap it again with curly braces. So, in your test, try passing FiltersAction as an object like below:
wrapper = shallow(
<ExpenseListFilters.wrappedComponent
FiltersAction = {{
setStartDate: setStartDateSpy,
setDentDate: setEndDateSpy,
setTextFilter: setTextFilterSpy
}}
RootStore = {FiltersStore}
/>
)
Note that in your example, you were:
Not double-wrapping your object w/ curly braces
Creating the object literal with computed property keys. That is, when you don't explicitly provide a property name, the resulting object will create the property name based on the variable name. Here is a good resource for learning more about enhanced object literals in ES6 So your generated object did not include the properties your application code was expecting and was attempting to call undefined as a function

how do I programmatically create instance of react component?

I have a table row component that checks to see if a custom component is specified and is supposed to programmatically take that React component (column.customComponent) and render the data using it. However, I cannot seem to instantiate the components.. all I get is the data as a string.
Here is what I have:
const cellContent = typeof(column.customComponent === 'undefined') ?
rowData[column.columnName] :
{
type: column.customComponent,
props: {
data: rowData[column.columnName],
rowData
}
};
console.log(cellContent); displays something like:
the function representation of the React component
or
function DateCell() ....
I'm not sure if I'm just using the wrong syntax when assigning cellContent or if I should be doing something with factories or what.
How should I do this?
I use this helper method to create components and return the instance.
static async renderComponentAt(componentClass, props, parentElementId){
let componentId = props.id;
if(!componentId){
throw Error('Component has no id property. Please include id:"...xyz..." to component properties.');
}
let parentElement = document.getElementById(parentElementId);
return await new Promise((resolve, reject) => {
props.ref = (component)=>{
if(component){
resolve(component);
}
};
let element = React.createElement(componentClass, props, null);
ReactDOM.render(element, parentElement);
});
}

Resources