You'll notice that the type of motion includes & HTMLMotionComponents & SVGMotionComponents, which means that motion can either be used as a function (for wrapping custom components), or as an object where the properties are all of the HTML and SVG element tags (e.g. motion.h6).
I expect Framer Motion uses the Proxy for standard HTML and SVG elements as an optimisation, so that the wrapped elements are only created when necessary (e.g. you may not need motion.h6 in your application).
Ultimately though, the expected rendered output of HTML and SVG motion elements should be:
<motion.div> -> <div>
<motion.ul> -> <ul>
<motion.li> -> <li>
<motion.h6> -> <h6>
...
If you try snapshotting a few non-<div> elements with your current custom function, you'll notice that they all render as <div>. This is due to the typeof Component === 'string' check, which returns <div> for all HTML and SVG element string values.
Here's my full solution, which includes a as typeof custom & DOMMotionComponents cast on the motion proxy, as per the actual motion proxy:
/* eslint-disable react/display-name */import{CustomDomComponent}from'framer-motion/types/render/dom/motion-proxy';import{DOMMotionComponents}from'framer-motion/types/render/dom/types';importReactfrom'react';constactual=jest.requireActual('framer-motion');// https://github.com/framer/motion/blob/main/src/render/dom/motion.tsfunctioncustom<Props>(Component:string|React.ComponentType<Props>):CustomDomComponent<Props>{// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignorereturnReact.forwardRef((props,ref)=>{// do not pass framer props to DOM elementconstregularProps=Object.entries(props).reduce((acc,[key,value])=>{if(!actual.isValidMotionProp(key))acc[key]=value;returnacc;},{});// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignorereturn<Componentref={ref}{...regularProps}/>;});}constcomponentCache=newMap<string,any>();constmotion=newProxy(custom,{get:(_target,key:string)=>{if(!componentCache.has(key)){componentCache.set(key,custom(key));}returncomponentCache.get(key)!;}})astypeofcustom&DOMMotionComponents;module.exports={...actual,AnimatePresence:({children}:{children:React.ReactChildren})=><>{children}</>,motion};
Thanks for this article and thanks Jason as well for the clarification! Indeed my tests started failing because motion elements were being mocked as divs.
This is working for me with Framer Motion 5.6. Maybe one day I'll be as smart as you guys and be able to come up with a mock like this. Will have to copy-paste this for now. 😅
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
The
motion
function fromframer-motion
has two use cases:motion.div
,motion.ul
,motion.li
, etc).const MotionComponent = motion(Component)
).You'll notice that the type of
motion
includes& HTMLMotionComponents & SVGMotionComponents
, which means thatmotion
can either be used as a function (for wrapping custom components), or as an object where the properties are all of the HTML and SVG element tags (e.g.motion.h6
).I expect Framer Motion uses the
Proxy
for standard HTML and SVG elements as an optimisation, so that the wrapped elements are only created when necessary (e.g. you may not needmotion.h6
in your application).Ultimately though, the expected rendered output of HTML and SVG motion elements should be:
<motion.div>
-><div>
<motion.ul>
-><ul>
<motion.li>
-><li>
<motion.h6>
-><h6>
If you try snapshotting a few non-
<div>
elements with your currentcustom
function, you'll notice that they all render as<div>
. This is due to thetypeof Component === 'string'
check, which returns<div>
for all HTML and SVG elementstring
values.Here's my full solution, which includes a
as typeof custom & DOMMotionComponents
cast on themotion
proxy, as per the actual motion proxy:I tried the above solution and overwriting AnimatePresence results in this error:
Error: Uncaught [TypeError: Cannot read property 'jsxDEV' of undefined]
I did some digging around and it looks like jsxDEV is from React. Any suggestions on how to resolve this?
Weird! Sorry nothing off the top of my head. I'll post here if I find anything.
Thanks for this article and thanks Jason as well for the clarification! Indeed my tests started failing because motion elements were being mocked as divs.
This is working for me with Framer Motion 5.6. Maybe one day I'll be as smart as you guys and be able to come up with a mock like this. Will have to copy-paste this for now. 😅