From 7f1647fadcd193c0d16e51b314d299ee19ae5746 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 19 Dec 2024 11:53:53 -0800 Subject: [PATCH] fix: handle cases when Animation.persist() does not exist (#33282) --- ...-f4f8b668-785c-48d9-9d8f-2410472b12f4.json | 7 ++++ .../factories/createMotionComponent.test.tsx | 39 ++++++++++++++++++ .../createPresenceComponent.test.tsx | 40 +++++++++++++++++++ .../library/src/hooks/useAnimateAtoms.ts | 12 +++++- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json diff --git a/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json b/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json new file mode 100644 index 00000000000000..81ed563ea2612c --- /dev/null +++ b/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: handle case when Animation.persist() does not exist", + "packageName": "@fluentui/react-motion", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx index 7106dde0423972..6eb1ad024ae2f9 100644 --- a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx @@ -38,6 +38,27 @@ function createElementMock() { } describe('createMotionComponent', () => { + let hasAnimation: boolean; + beforeEach(() => { + if (!global.Animation) { + hasAnimation = false; + global.Animation = { + // @ts-expect-error mock + prototype: { + persist: jest.fn(), + }, + }; + } else { + hasAnimation = true; + } + }); + + afterEach(() => { + if (!hasAnimation) { + // @ts-expect-error mock + delete global.Animation; + } + }); it('creates a motion and plays it', () => { const TestAtom = createMotionComponent(motion); const { animateMock, ElementMock } = createElementMock(); @@ -54,6 +75,24 @@ describe('createMotionComponent', () => { }); }); + it('creates a motion and plays it (without .persist())', () => { + // @ts-expect-error mock + delete global.Animation.prototype.persist; + const TestAtom = createMotionComponent(motion); + const { animateMock, ElementMock } = createElementMock(); + + render( + + + , + ); + + expect(animateMock).toHaveBeenCalledWith(motion.keyframes, { + duration: 500, + fill: 'forwards', + }); + }); + it('supports functions as motion definitions', () => { const fnMotion = jest.fn().mockImplementation(() => motion); const TestAtom = createMotionComponent(fnMotion); diff --git a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx index 77472e8c855ba3..7d09f1af50647e 100644 --- a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx @@ -44,6 +44,28 @@ function createElementMock() { } describe('createPresenceComponent', () => { + let hasAnimation: boolean; + beforeEach(() => { + if (!global.Animation) { + hasAnimation = false; + global.Animation = { + // @ts-expect-error mock + prototype: { + persist: jest.fn(), + }, + }; + } else { + hasAnimation = true; + } + }); + + afterEach(() => { + if (!hasAnimation) { + // @ts-expect-error mock + delete global.Animation; + } + }); + describe('appear', () => { it('does not animate by default', () => { const TestPresence = createPresenceComponent(motion); @@ -71,6 +93,24 @@ describe('createPresenceComponent', () => { expect(animateMock).toHaveBeenCalledWith(enterKeyframes, options); }); + it('animates when is "true" (without .persist())', () => { + // @ts-expect-error mock + delete window.Animation.prototype.persist; + const TestPresence = createPresenceComponent(motion); + const { animateMock, ElementMock } = createElementMock(); + + render( + + + , + ); + + expect(animateMock).toHaveBeenCalledWith(enterKeyframes, { + ...options, + duration: 500, + }); + }); + it('finishes motion when wrapped in motion behaviour context with skip behaviour', async () => { const TestPresence = createPresenceComponent(motion); const { finishMock, ElementMock } = createElementMock(); diff --git a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts index cb0a03544d3a2c..9bc31e0d2e9cee 100644 --- a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts +++ b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts @@ -2,6 +2,9 @@ import * as React from 'react'; import type { AnimationHandle, AtomMotion } from '../types'; function useAnimateAtomsInSupportedEnvironment() { + // eslint-disable-next-line @nx/workspace-no-restricted-globals + const SUPPORTS_PERSIST = typeof window !== 'undefined' && typeof window.Animation?.prototype.persist === 'function'; + return React.useCallback( ( element: HTMLElement, @@ -22,7 +25,12 @@ function useAnimateAtomsInSupportedEnvironment() { ...(isReducedMotion && { duration: 1 }), }); - animation.persist(); + if (SUPPORTS_PERSIST) { + animation.persist(); + } else { + const resultKeyframe = keyframes[keyframes.length - 1]; + Object.assign(element.style ?? {}, resultKeyframe); + } return animation; }); @@ -75,7 +83,7 @@ function useAnimateAtomsInSupportedEnvironment() { }, }; }, - [], + [SUPPORTS_PERSIST], ); }