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
motionfunction fromframer-motionhas two use cases:motion.div,motion.ul,motion.li, etc).const MotionComponent = motion(Component)).You'll notice that the type of
motionincludes& HTMLMotionComponents & SVGMotionComponents, which means thatmotioncan 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
Proxyfor standard HTML and SVG elements as an optimisation, so that the wrapped elements are only created when necessary (e.g. you may not needmotion.h6in 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 currentcustomfunction, 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 elementstringvalues.Here's my full solution, which includes a
as typeof custom & DOMMotionComponentscast on themotionproxy, 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. 😅