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
Related
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();
});
Hi I have a functional component as shown below:
import React, { useRef, useEffect, useState } from 'react';
const SomeComponent = ({ prop1, ...otherProps}) => {
const divRef = useRef();
useEffect(() => {
divRef.current.addEventListener('mousedown', mouseDownFunc);
}, []);
const mouseDownFunc = () => {
document.addEventListener('mousemove', (el) => {
// call some parent function
});
}
return (
<div
className='test-div'
ref={ divRef }>
</div>
);
};
How do I test a react functional component wherein addEventListener is added using ref inside useEffect which when triggered calls mouseDownFunc.
I'm new to react jest testing, little confused on how to do it.
Testing this sort of component can be tricky, but using #testing-library/react I think I was able to come up with something useful.
I did have to make some changes to your component to expose the API a bit, and I also made some changes so that it stops listening to the events on mouseup which may not be the specific event you want.
Here's the modified component:
// MouseDownExample.js
import React, { useEffect, useState } from "react";
export default ({ onMouseMoveWhileDown }) => {
const [x, setX] = useState(null);
const [listening, setListening] = useState();
// Replaced with mouse move function, should make sure we're unlistening as well
useEffect(() => {
if (listening) {
const onMouseMove = (event) => {
// call some parent function
onMouseMoveWhileDown(event);
console.log(event.clientX);
// purely for testing purposes
setX(event.clientX);
};
const onMouseUp = (event) => {
// stop listening on mouse up
// - you should pick whatever event you want to stop listening
// - this is global so it also stops when the mouse is outside the box
setListening(false);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}
}, [listening, onMouseMoveWhileDown]);
return (
<div
style={{
backgroundColor: "red",
width: 200,
height: 200
}}
className="test-div"
onMouseDown={() => {
// moved this inline, so no ref
setListening(true);
}}
>
X Position: {x}
</div>
);
};
I called out in comments the main differences.
And here's an example test:
// MouseDownExample.test.js
import React from "react";
import { fireEvent, render } from "#testing-library/react";
import MouseDownExample from "./MouseDownExample";
it("shouldn't trigger onMouseMoveWhileDown when mouse isn't down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseMove(
document,
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
{
clientX: 200
}
);
// hasn't gone down yet
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
fireEvent.mouseDown(subject);
fireEvent.mouseUp(subject);
// went down then up before moving
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
});
it("should trigger onMouseMoveWhileDown when mouse is down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseDown(subject);
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).toHaveBeenCalledWith(
expect.objectContaining({ clientX: 200 })
);
});
What's happening here, is we're rendering the component, then firing events to ensure the onMouseMoveWhileDown function prop is called when we expect.
We have to do expect.objectContaining rather than just the object because it's called with a MouseEvent which contains other properties.
Another test we might want to add is an unmount test to ensure the listeners are no longer triggering events.
You can look at/experiment with this Code Sandbox with this component and the tests. Hope this helps 👍
Hi guys) I have a strange question may be, but I'm at a dead end.
I have my own custom hook.
const useModal = (Content?: ReactNode, options?: ModalOptions) => {
const { isOpen, close: contextClose, open: contextOpen, setContent } = useContext(
ModalContext,
)
const [customOpenContent, setCustomOpenContent] = useState<ReactNode>()
const showModal = useCallback(
(customContent?: ReactNode) => {
if (!isNil(customContent)) {
setCustomOpenContent(customContent)
contextOpen(customContent, options)
} else contextOpen(Content, options)
},
[contextOpen, Content, options],
)
const hideModal = useCallback(() => {
contextClose()
}, [contextClose])
return { isOpen, close: hideModal, open: showModal, setContent }
}
It is quite simple.
Also i have component which uses this hook
const App: React.FC = () => {
const [loading, setLoading] = useState(false)
const { open } = useModal(null, { deps: [loading] })
useEffect(() => {
setTimeout(() => {
setLoading(true)
}, 10000)
})
const buttonCallback = useCallback(() => {
open(<Button disabled={!loading}>Loading: {loading.toString()}</Button>)
}, [loading, open])
return (
<Page title="App">
<Button onClick={buttonCallback}>Open Modal</Button>
</Page>
)
}
Main problem is - Button didn't became enabled because useModal hook doesn't know anything about changes.
May be you have an idea how to update this component while it's props are updated? And how to do it handsomely ))
Context isn't the best solution to this problem. What you want is a Portal instead. Portals are React's solution to rendering outside of the current React component hierarchy. How to use React Portal? is a basic example, but as you can see, just going with the base React.Portal just gives you the location to render.
Here's a library that does a lot of the heavy lifting for you: https://github.com/wellyshen/react-cool-portal. It has typescript definitions and provides an easy API to work with.
Here's your example using react-cool-portal.
import usePortal from "react-cool-portal";
const App = () => {
const [loading, setLoading] = useState(false);
const { Portal, isShow, toggle } = usePortal({ defaultShow: false });
useEffect(() => {
setTimeout(() => {
setLoading(true);
}, 10000);
});
const buttonCallback = useCallback(() => {
toggle();
}, [toggle]);
return (
<div title="App" style={{ backgroundColor: "hotpink" }}>
<button onClick={buttonCallback}>
{isShow ? "Close" : "Open"} Modal
</button>
<Portal>
<button disabled={!loading}>Loading: {loading.toString()}</button>
</Portal>
<div>{loading.toString()}</div>
</div>
);
};
Basic CodeSandbox Example
There are more detailed ones within the react-cool-portal documentation.
For more detail of the issues with the Context solution you were trying, is that React Elements are just a javascript object. React then uses the object, it's location in the tree, and it's key to determine if they are the same element. React doesn't actually care or notice where you create the object, only it's location in the tree when it is rendered.
The disconnect in your solution is that when you pass the element to the open function in buttonCallback, the element is created at that point. It's a javascript object that then is set as the content in your context. At that point, the object is set and won't change until you called open again. If you set up your component to call open every time the relevant state changes, you could get it working that way. But as I mentioned earlier, context wasn't built for rendering components outside of the current component; hence why some really weird workarounds would be required to get it working.
I have this simple fallbackImage Component:
export interface ImageProps {
srcImage: string;
classNames?: string;
fallbackImage?: FallbackImages;
}
const Image = ({
srcImage,
classNames,
fallbackImage = FallbackImages.FALLBACK
}: ImageProps) => {
const imgToSourceFrom = srcImage;
const imgToFallbackTo = fallbackImage;
const imageRef = useRef(null);
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {};
};
return (
<img ref={imageRef} src={imgToSourceFrom} className={classNames} onError={whenImageIsMissing} />
);
};
export default Image;
It works perfectly. I have test running for it with Jest and React-Testing-Library. I have tested all but one scenario. This one:
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {}; // This line.
};
This line basically prevents an infinite Loop in case both images are missing
The Problem:
I want to test that my onerror function has been called exactly one time. Which I am really stuck on how to do it. Here is the test...
const { container } = render(<Image srcImage={undefined} fallbackImage={undefined} />);
const assertion = container.querySelector('img').onerror;
fireEvent.error(container.firstElementChild);
console.log(container.firstElementChild);
expect(container.firstElementChild.ref.current.onerror).toHaveBeenCalledTimes(1);
// This though has no reference to a real value. Is an example of what I want to get at.
The Question:
How to access the ref callback function and check how many times has my function been called?
Any ideas on this. I am at a loss, I tried mocking refs, I tried mocking and spying on the component. I tried using act and async/await, in case it was called after. I really need some help on this..
You should check if your function is called or not, that's called testing implementation details, rather you should check if your img element have correct src.
Even you should add some alt and user getByAltText to select image element
const { getByAltText } = render(<Image srcImage={undefined} fallbackImage={undefined} />);
const imageElement = getByAltText('Image Alt');
fireEvent.error(imageElement);
expect(imageElement.src).toEqual(imgToFallbackTo);
You have 2 options:
Add a callback to your props that will be called when whenImageIsMissing is called:
export interface ImageProps {
srcImage: string;
classNames?: string;
fallbackImage?: FallbackImages;
onImageMissing?:();
}
const Image = ({
srcImage,
classNames,
onImageMissing,
fallbackImage = FallbackImages.FALLBACK
}: ImageProps) => {
const imgToSourceFrom = srcImage;
const imgToFallbackTo = fallbackImage;
const imageRef = useRef(null);
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {};
if (onImageMissing) onImageMissing();
};
return (
<img ref={imageRef} src={imgToSourceFrom} className={classNames} onError={whenImageIsMissing} />
);
};
and then insert jest.fn in your test and check how many times it was called.
The other option is to take the implementation of whenImageIsMissing and put it inside image.util file and then use jest.spy to get number of calls. Since you are using a function component there is no way to access this function directly.
Hope this helps.
I'm trying to get a reference to the leaflet object using the hook, so I can query the new map boundaries on different events (like Map.getBoundaries()). I'm pretty new to reac-leaflet, and this approach might be completely wrong, but this is what I've got for now...
What I'm trying to do is to get the map boundaries on each moveend event, if that's helpful...
First of all, you can only use the hook in a component that's inside the Map element:
<Map>
<YourComponent />
</Map
And then inside your component you can do something like:
const YourComponent = () => {
const { map } = useLeaflet();
const [bounds, setBounds] = React.useState({});
React.useEffect(() => {
const eventHandler = event => {
setBounds(event.target.getBounds());
doSomethingElse();
}
map.on("moveend", eventHandler);
return () => {
map.off("moveend", eventHandler); // Remove event handler to avoid creating multiple handlers
}
}, [setBounds, map]);
return {
// Use bounds for whatever you need
<div>Lat: {bounds.lat}; long: {bounds.lng}</div>
}
}
Handler:
const onMoveEnd = (event) => {
const bounds = event.target.getBounds()
console.log(bounds)
}
// _northEast: LatLng {lat: 47.51470804161579, lng: 19.071493148803714}
// _southWest: ...
Component:
<Map onmoveend={onMoveEnd}></Map>