how to use spyOn on a class less component - reactjs

I am trying to apply spyOn to check whether my fucntion download is called on mouse click but I am getting the error. I am already follwoing this question but still no leads. Can anyone tell me where I went wrong. I cannot figure out any clue.
Error
Argument of type '"download"' is not assignable to parameter of type '"context"'.
mcb = jest.spyOn(fileDownlaod.instance(), "download");
my react component is:
const Filer = ({Filey} ) => {
const download = () => {
Filey()
.then((res: Response) => res.blob())
.then((data: Blob) => {
const URL = URL.createObjectURL(data);
});
};
return (
<>
<button
onMouseOver={() => download()}
onClick={() => download()}
>
</button>
</>
);
};
export default Filer;
my jest test is :
import React from 'react';
import Filer from './Filer';
import { mount, ReactWrapper } from 'enzyme';
let filer: ReactWrapper<any>;
describe('Filer', () => {
it('clicked download', () => {
filer = mount(
<Filer />
);
const _download = () => {
//some thing
}
mcb = jest.spyOn(filer.instance(), "download").mockImplementation(_download);
filer.find('button').simulate('click')
expect(mcb.mock.calls.length).toEqual(1);
});
});

If you look at the answer you are already following. In the end it has mentioned that spyOn does not work on functional components inner functions.
This is what has been said:
Keep in mind that any methods scoped within your functional component are not available for spying
So you can spy on props passed.
So the correct implementation that should work, can be:
it('clicked download', () => {
Filey = jest.fn().mockImplementation(_Filey)
filer = mount(
<Filer Filey={Filey}/>
);
expect(Filey).toHaveBeenCalled();
});

Related

How do I write this test for complete coverage?

I am new to React development and am studying testing with Jest and React Testing Library (RTL).
But I'm having difficulty doing the complete coverage of the component below:
import {
CustomCardActions,
CustomCardHeader,
} from '#Custom/react';
import React from 'react';
import {
PortalAccessButton,
PortalAccessContext,
PortalAccessInternalCard,
PortalAccessTitle,
} from './styles';
interface PortalAccessCard {
children: React.ReactNode
buttonText: string;
hrefLink: string;
}
export const redirectToUrl = (hrefLink: string) => {
window.open(hrefLink, '_self');
};
const PortalAccessCard = (props: PortalAccessCard) => {
const { children, buttonText, hrefLink } = props;
return (
<PortalAccessContext inverse>
<PortalAccessInternalCard>
<CustomCardHeader>
<PortalAccessTitle variant="heading-4">
{children}
</PortalAccessTitle>
</CustomCardHeader>
<CustomCardActions>
<PortalAccessButton onCustomClick={() => redirectToUrl(hrefLink)}>
{buttonText}
</PortalAccessButton>
</CustomCardActions>
</PortalAccessInternalCard>
</PortalAccessContext>
);
};
export default React.memo(PortalAccessCard);
There are two details here:
1- I exported the "redirectToUrl" method to be able to test it. I can't say if there's a better way out, but maybe the second question solves this one.
2- When I check the coverage report it says that this part () => redirectToUrl(hrefLink) has not been tested, but it is basically the pointer to the method I exported above.
My test looks like this:
import { render, RenderResult } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import PortalAccessCard from '.';
import * as PortalAccessCardComponent from '.';
describe('PortalAccessCard', () => {
let renderResult: RenderResult;
const hrefLink = '#';
beforeEach(() => {
renderResult = render(
<PortalAccessCard
buttonText="Texto do botão"
hrefLink={hrefLink}
>
Texto interno PortalAccessCard.
</PortalAccessCard>,
);
});
it('should call onCustomClick and redirectToUrl', async () => {
window.open = jest.fn();
jest.spyOn(PortalAccessCardComponent, 'redirectToUrl');
const onCustomClick = jest.fn(() => PortalAccessCardComponent.redirectToUrl(hrefLink));
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
CustomButtonElement.onclick = onCustomClick;
await userEvent.click(CustomButtonElement);
expect(onCustomClick).toBeCalledTimes(1);
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledTimes(1);
});
});
What can I do to make the test call of the onCustomClick event call the redirectToUrl method so that Jest understands that this snippet has been tested?
Not sure which exactly line is not covered... Though, toBeCalledTimes is a sign of bad test expectation, so try to append to the very bottom line:
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledWith(hrefLink);
It's better to test for the side effect you want (opening a window). redirectToUrl is an implementation detail. I think you're making this much harder than it needs to be.
Spy on window.open, click the item, check the spy. I think that's all you need.
jest.spyOn(window, 'open')
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
await userEvent.click(CustomButtonElement);
// or maybe: getByRole('something...').click()
expect(window.open).toHaveBeenCallWith('#', '_self')

How to call react function from external JavaScript file

I have read this post [ https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/ ]
But I don't understand.
that is not working on my source code.
it's my tsx file
declare global {
interface Window {
fn_test(): void;
childComponent: HTMLDivElement; <-- what the... ref type????
}
}
export default function Contact(): React.ReactElement {
....
function file_input_html( i:number ): React.ReactElement {
return (
<form id={`frm_write_file_${i}`} .... </form>
)
}
....
return (
<div ref={(childComponent) => {window.childComponent = childComponent}}>
....
)
it's my external javascript file
function fn_test(){
window.childComponent.file_input_html(3)
var element = document.getElementById("ifrm_write_file");
// element.value = "mystyle";
}
How can i call file_input_html function?
plase help me ...
You have some logic here that doesn't completely make sense.
In your class, you define file_input_html, which returns a component.
Then, in fn_test, you call attempt to call that function (which doesn't work -- I'll address that in a minute), but you don't do anything with the output.
The article that you linked to tells you how to get a ref to a component (eg the div in this case) -- not the actual Contact, which doesn't have a property named file_input_html anyway -- that's just a function you declared inside its scope.
What I'm assuming you want to happen (based on the code you shared) is for your external javascript file to be able to tell your component to render a form with a certain ID and then be able to get a reference to it. Here's an example of how to do this (it's a little convoluted, but it's a funny situation):
const { useState } = React
const App = (props) => {
const [formId, setFormId] = useState(2)
useEffect(() => {
window.alterFormId = setFormId
},[])
return (<div id={"form" + formId} ref={(ourComponent) => {window.ourComponent = ourComponent}}>
Text {formId}
</div>);
}
setTimeout(() => {
window.alterFormId(8);
setTimeout(() => {
console.log(window.ourComponent)
window.ourComponent.innerText = "Test"
}, 20)
}, 1000)
ReactDOM.render(<App />,
document.getElementById("root"))
What's happening here:
In useEffect, I set alterFormId on window so that it can be used outside of the React files
Using the technique you linked to, I get a ref to the div that's created. Note that I'm setting the ID here as well, based on the state of formId
The setTimeout function at the end tests all this:
a) It waits until the first render (the first setTimeout) and then calls alterFormId
b) Then, it waits again (just 20ms) so that the next run loop has happened and the React component has re-rendered, with the new formId and reference
c) From there, it calls a method on the div just to prove that the reference works.
I'm not exactly sure of your use case for all this and there are probably easier ways to architect things to avoid these issues, but this should get you started.
안녕하세요. 자바스크립트로 흐름만 알려드리겠습니다
아래 코드들을 참고해보세요.
iframe간 통신은
window.postMessage API와
window.addEventListener('message', handler) 메시지 수신 이벤트 리스너 로 구현할 수있습니다. 보안관련해서도 방어로직이 몇줄 필요합니다(origin 체크 등)
in parent
import React from 'react';
export function Parent () {
const childRef = React.useRef(null);
const handleMessage = (ev) => {
// 방어로직들
if (check ev.origin, check ev.source, ....) {
return false;
}
console.log('handleMessage(parent)', ev)
}
React.useEffect(() => {
window.addEventListener('message', handleMessage);
// clean memory
return () => {
window.removeEventListener('message', handleMessage);
}
})
return (
<div>
<iframe ref="childRef" src="child_src" id="iframe"></iframe>
</div>
)
}
in child
import React from 'react';
export function Iframe () {
const handleMessage = (ev) => {
console.log('handleMessage(child)', ev)
}
const messagePush = () => {
window.parent.postMessage({ foo: 'bar' }, 'host:port')
}
React.useEffect(() => {
window.addEventListener('message', handleMessage);
// clean memory
return () => {
window.removeEventListener('message', handleMessage);
}
})
return (
<div>
<button onClick={messagePush}>Push message</button>
</div>
)
}

Set React Context inside function-only component

My goal is very simple. I am just looking to set my react context from within a reusable function-only (stateless?) react component.
When this reusable function gets called it will set the context (state inside) to values i provide. The problem is of course you can't import react inside a function-only component and hence I cannot set the context throughout my app.
There's nothing really to show its a simple problem.
But just in case:
<button onCLick={() => PlaySong()}></button>
export function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //cannot call useContext in this component
}
If i use a regular react component, i cannot call this function onClick:
export default function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //fine
}
But:
<button onCLick={() => <PlaySong />}></button> //not an executable function
One solution: I know i can easily solve this problem by simply creating a Playbtn component and place that in every song so it plays the song. The problem with this approach is that i am using a react-player library so i cannot place a Playbtn component in there...
You're so close! You just need to define the callback inside the function component.
export const PlaySongButton = ({...props}) => {
const {setCurrentSong} = useContext(StoreContext);
const playSong = () => {
setCurrentSong("some song");
}
return (
<button
{...props}
onClick={() => playSong()}
/>
)
}
If you want greater re-usability, you can create custom hooks to consume your context. Of course where you use these still has to follow the rules of hooks.
export const useSetCurrentSong = (song) => {
const {setCurrentSong} = useContext(StoreContext);
setCurrentSong(song);
}
It is possible to trigger a hook function by rendering a component, but you cannot call a component like you are trying to do.
const PlaySong = () => {
const {setCurrentSong} = useContext(StoreContext);
useEffect( () => {
setCurrentSong("some song");
}, []
}
return null;
}
const MyComponent = () => {
const [shouldPlay, setShouldPlay] = useState(false);
return (
<>
<button onClick={() => setShouldPlay(true)}>Play</button>
{shouldPlay && <PlaySong />}
</>
)
}

p5.js mousePressed event returns Undefined

I cant get mousePressed to console log the MouseEvent. I'm not even sure if having it in the setup function is the right place?
https://codesandbox.io/embed/withered-monad-jrhyw
import React from 'react';
import Sketch from 'react-p5';
const App: React.FC = () => {
const setup = (p5: any) => {
canvas = p5.createCanvas(window.innerWidth, window.innerHeight);
canvas.mouseWheel((e: MouseEvent) => {
console.log(e); // Wheel Event
});
canvas.mousePressed((e: MouseEvent) => {
console.log(e); // Undefined
});
};
const draw = (p5: any) => {
p5.background(50);
p5.fill(0);
p5.rect(25, 25, 50, 50);
};
return (
<React.Fragment>
<Sketch setup={setup} draw={draw} />
</React.Fragment>
);
};
export { App };
The readme of the react-p5 library indicates that you should use the mousePressed method as a prop, so try and define it outside of setup and then pass it:
const mp = (e: MouseEvent) => {
console.log(e);
})
<Sketch setup={setup} draw={draw} mousePressed={mp}/>
Nevertheless, I did try defining several other methods inside of setup and all seemed to work normally; mousePressed was the only one that required to be passed as a prop.
I have answered this question on opened GitHub Issue in order to use only one channel to track this issue and resolve it. Github Issue #16

Jest Invariant Violation

Simple use of react hooks in beta do not work when using jest and result in an error of
Invariant Violation: Hooks can only be called inside the body of a function component.
Despite much looking around I do not see working examples of Jest with react hooks. Yes I know it is beta but let us flag it now.
https://github.com/ghinks/jest-react-hook-issue.git
const MyDiv = () => {
const [count, setCount] = useState(0)
const clickHandler = () => { setCount(count + 1);
console.log(`clicked ${count}`) }
return (
<div onClick={clickHandler}>
ClickMe {count}
</div>
)
}
even simple tests
import { MyDiv } from './App';
describe('Test Component using hooks', () => {
test('MyDiv', () => {
const div = MyDiv();
expect(div).toMatchSnapshot();
})
});
will fail with the invariant error.
I would expect this to actually work.
looks like simple change to
describe('Test Component using hooks', () => {
test('MyDiv', () => {
expect(<MyDiv/>).toMatchSnapshot();
})
});
resolves this
The problem is what the error says. Function components aren't supposed to be called directly.
It should be as the reference shows:
import renderer from 'react-test-renderer';
...
const myDiv = renderer.create(<MyDiv/>).toJSON();
expect(myDiv).toMatchSnapshot();

Resources