Skip to content

Commit dc9b792

Browse files
scttcpergeorge-sentry
authored andcommitted
fix(test): Upgrade framer motion, Disable animations in tests (#112270)
Skip all framer-motion animations in tests via `MotionGlobalConfig.skipAnimations = true` [docs](motiondivision/motion@3dffb40) so components render immediately without waiting for animation frames or transitions. Also bumps framer-motion from 12.23.12 to 12.38.0. ## Drawer test benchmarks (before → after) | Test suite | Before | After | Speedup | |---|---|---|---| | `globalDrawer/index.spec.tsx` | 3.01s | 1.20s | 2.5x | | `eventFeatureFlagSection.spec.tsx` | 2.66s | 2.30s | 1.2x | | `breadcrumbsDataSection.spec.tsx` | 1.95s | 1.58s | 1.2x | This also benefits every other test that renders framer-motion components (modals, tooltips, any `AnimatePresence` usage).
1 parent 6c32e08 commit dc9b792

File tree

6 files changed

+57
-47
lines changed

6 files changed

+57
-47
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@
171171
"echarts-for-react": "3.0.6",
172172
"esbuild": "0.25.10",
173173
"focus-trap": "7.6.5",
174-
"framer-motion": "12.23.12",
174+
"framer-motion": "12.38.0",
175175
"fuse.js": "^6.6.2",
176176
"gettext-parser": "7.0.1",
177177
"gl-matrix": "3.4.4",

pnpm-lock.yaml

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/app/views/issueList/actions/index.spec.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,24 @@ describe('IssueListActions', () => {
108108
expect(screen.queryByRole('button', {name: 'Archive'})).not.toBeInTheDocument();
109109
});
110110

111-
it('shows action buttons when any items are selected', () => {
111+
it('shows action buttons when any items are selected', async () => {
112112
render(<WrappedComponent selectedIds={['1']} />);
113113

114-
expect(screen.getByRole('button', {name: 'Resolve'})).toBeEnabled();
114+
expect(await screen.findByRole('button', {name: 'Resolve'})).toBeEnabled();
115115
expect(screen.getByRole('button', {name: 'Archive'})).toBeEnabled();
116116
});
117117

118-
it('shows select all checkbox as checked when all items are selected', () => {
118+
it('shows select all checkbox as checked when all items are selected', async () => {
119119
render(<WrappedComponent selectedIds={['1', '2', '3']} />);
120120

121121
// When all selected, label changes to "Deselect all"
122-
expect(screen.getByRole('checkbox', {name: 'Deselect all'})).toBeChecked();
122+
expect(await screen.findByRole('checkbox', {name: 'Deselect all'})).toBeChecked();
123123
});
124124

125-
it('shows select all checkbox as indeterminate when some items are selected', () => {
125+
it('shows select all checkbox as indeterminate when some items are selected', async () => {
126126
render(<WrappedComponent selectedIds={['1']} />);
127127

128-
const checkbox = screen.getByRole('checkbox', {name: 'Select all'});
128+
const checkbox = await screen.findByRole('checkbox', {name: 'Select all'});
129129
expect(checkbox).toBePartiallyChecked();
130130
});
131131
});

static/app/views/navigation/index.desktop.spec.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -851,9 +851,8 @@ describe('desktop navigation', () => {
851851

852852
await userEvent.hover(screen.getByRole('link', {name: 'Explore'}));
853853

854-
expect(
855-
await within(secondaryNav).findByRole('link', {name: 'Traces'})
856-
).toBeInTheDocument();
854+
// Re-query secondary nav because AnimatePresence remounts it with a new key
855+
expect(await screen.findByRole('link', {name: 'Traces'})).toBeInTheDocument();
857856
});
858857

859858
it('shows hovered group content in the peek view when sidebar is collapsed', async () => {

static/app/views/seerExplorer/explorerPanel.spec.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,15 @@ describe('ExplorerPanel', () => {
125125
});
126126

127127
describe('Feature Flag and Organization Checks', () => {
128-
it('renders when feature flag and open membership are enabled', () => {
128+
it('renders when feature flag and open membership are enabled', async () => {
129129
renderWithPanelContext(<ExplorerPanel />, true, {organization});
130130

131131
expect(
132-
screen.getByText(/Ask Seer anything about your application./)
132+
await screen.findByText(/Ask Seer anything about your application./)
133133
).toBeInTheDocument();
134134
});
135135

136-
it('does not render when feature flag is disabled', () => {
136+
it('does not render when feature flag is disabled', async () => {
137137
const disabledOrg = OrganizationFixture({
138138
features: [],
139139
hideAiFeatures: false,
@@ -144,10 +144,10 @@ describe('ExplorerPanel', () => {
144144
organization: disabledOrg,
145145
});
146146

147-
expect(container).toBeEmptyDOMElement();
147+
await waitFor(() => expect(container).toBeEmptyDOMElement());
148148
});
149149

150-
it('does not render when AI features are hidden', () => {
150+
it('does not render when AI features are hidden', async () => {
151151
const disabledOrg = OrganizationFixture({
152152
features: ['seer-explorer'],
153153
hideAiFeatures: true,
@@ -158,10 +158,10 @@ describe('ExplorerPanel', () => {
158158
organization: disabledOrg,
159159
});
160160

161-
expect(container).toBeEmptyDOMElement();
161+
await waitFor(() => expect(container).toBeEmptyDOMElement());
162162
});
163163

164-
it('does not render when open membership is disabled', () => {
164+
it('does not render when open membership is disabled', async () => {
165165
const disabledOrg = OrganizationFixture({
166166
features: ['seer-explorer'],
167167
hideAiFeatures: false,
@@ -172,28 +172,30 @@ describe('ExplorerPanel', () => {
172172
organization: disabledOrg,
173173
});
174174

175-
expect(container).toBeEmptyDOMElement();
175+
await waitFor(() => expect(container).toBeEmptyDOMElement());
176176
});
177177
});
178178

179179
describe('Empty State', () => {
180-
it('shows empty state when no messages exist', () => {
180+
it('shows empty state when no messages exist', async () => {
181181
renderWithPanelContext(<ExplorerPanel />, true, {organization});
182182

183183
expect(
184-
screen.getByText(/Ask Seer anything about your application./)
184+
await screen.findByText(/Ask Seer anything about your application./)
185185
).toBeInTheDocument();
186186
});
187187

188-
it('shows input section in empty state', () => {
188+
it('shows input section in empty state', async () => {
189189
renderWithPanelContext(<ExplorerPanel />, true, {organization});
190190

191191
expect(
192-
screen.getByPlaceholderText('Type your message or / command and press Enter ↵')
192+
await screen.findByPlaceholderText(
193+
'Type your message or / command and press Enter ↵'
194+
)
193195
).toBeInTheDocument();
194196
});
195197

196-
it('shows error when hook returns isError=true', () => {
198+
it('shows error when hook returns isError=true', async () => {
197199
const useSeerExplorerSpy = jest
198200
.spyOn(useSeerExplorerModule, 'useSeerExplorer')
199201
.mockReturnValue({
@@ -220,7 +222,7 @@ describe('ExplorerPanel', () => {
220222
renderWithPanelContext(<ExplorerPanel />, true, {organization});
221223

222224
expect(
223-
screen.getByText('Error loading this session (ID=123).')
225+
await screen.findByText('Error loading this session (ID=123).')
224226
).toBeInTheDocument();
225227
expect(
226228
screen.queryByText(/Ask Seer anything about your application./)
@@ -231,7 +233,7 @@ describe('ExplorerPanel', () => {
231233
});
232234

233235
describe('Messages Display', () => {
234-
it('renders messages when session data exists', () => {
236+
it('renders messages when session data exists', async () => {
235237
const mockSessionData = {
236238
blocks: [
237239
{
@@ -283,7 +285,7 @@ describe('ExplorerPanel', () => {
283285

284286
renderWithPanelContext(<ExplorerPanel />, true, {organization});
285287

286-
expect(screen.getByText('What is this error?')).toBeInTheDocument();
288+
expect(await screen.findByText('What is this error?')).toBeInTheDocument();
287289
expect(
288290
screen.getByText('This error indicates a null pointer exception.')
289291
).toBeInTheDocument();
@@ -533,19 +535,21 @@ describe('ExplorerPanel', () => {
533535
openMembership: true,
534536
});
535537

536-
it('does not render the toggle when the feature flag is disabled', () => {
538+
it('does not render the toggle when the feature flag is disabled', async () => {
537539
renderWithPanelContext(<ExplorerPanel />, true, {organization});
538540

541+
// Wait for effects to settle before asserting absence
542+
await screen.findByTestId('seer-explorer-input');
539543
expect(
540544
screen.queryByRole('checkbox', {name: 'Toggle context engine'})
541545
).not.toBeInTheDocument();
542546
});
543547

544-
it('renders the toggle when the feature flag is enabled', () => {
548+
it('renders the toggle when the feature flag is enabled', async () => {
545549
renderWithPanelContext(<ExplorerPanel />, true, {organization: orgWithFlag});
546550

547551
expect(
548-
screen.getByRole('checkbox', {name: 'Toggle context engine'})
552+
await screen.findByRole('checkbox', {name: 'Toggle context engine'})
549553
).toBeInTheDocument();
550554
});
551555

@@ -623,20 +627,20 @@ describe('ExplorerPanel', () => {
623627
});
624628

625629
describe('Visibility Control', () => {
626-
it('renders when isVisible=true', () => {
630+
it('renders when isVisible=true', async () => {
627631
renderWithPanelContext(<ExplorerPanel />, true, {organization});
628632

629-
expect(screen.getByTestId('seer-explorer-input')).toBeInTheDocument();
633+
expect(await screen.findByTestId('seer-explorer-input')).toBeInTheDocument();
630634
});
631635

632-
it('can handle visibility changes', () => {
636+
it('can handle visibility changes', async () => {
633637
const {rerenderWithOpen} = renderWithPanelContext(<ExplorerPanel />, false, {
634638
organization,
635639
});
636640

637641
rerenderWithOpen(true);
638642

639-
expect(screen.getByTestId('seer-explorer-input')).toBeInTheDocument();
643+
expect(await screen.findByTestId('seer-explorer-input')).toBeInTheDocument();
640644
});
641645
});
642646
});

tests/js/setup.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212

1313
import {type ReactElement} from 'react';
1414
import {configure as configureRtl} from '@testing-library/react'; // eslint-disable-line no-restricted-imports
15+
import {MotionGlobalConfig} from 'framer-motion';
1516
import {enableFetchMocks} from 'jest-fetch-mock';
1617
import {ConfigFixture} from 'sentry-fixture/config';
1718

@@ -41,6 +42,12 @@ enableFetchMocks();
4142
// See https://github.com/jsdom/jsdom/issues/1330
4243
SVGElement.prototype.getTotalLength ??= () => 1;
4344

45+
/**
46+
* Skip all framer-motion animations in tests so components render immediately
47+
* without waiting for animation frames or transitions.
48+
*/
49+
MotionGlobalConfig.skipAnimations = true;
50+
4451
/**
4552
* React Testing Library configuration to override the default test id attribute
4653
*

0 commit comments

Comments
 (0)