Skip to content

Commit 59578f6

Browse files
committed
fix Animated in react strict mode usage
1 parent dbe59ad commit 59578f6

3 files changed

Lines changed: 602 additions & 3 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
vi.unmock('@formkit/auto-animate/react');
4+
vi.unmock('@formkit/auto-animate');
5+
6+
import { bindCreateFixtures } from '@/test/create-fixtures';
7+
import { render, screen, waitFor } from '@/test/utils';
8+
9+
import { clearFetchCache } from '../../hooks';
10+
import { Account } from '../UserProfile/Account';
11+
import { AccountEmails, AccountProfile } from '../UserProfile/sectionWrappers';
12+
13+
const { createFixtures } = bindCreateFixtures('UserProfile');
14+
15+
function findAddAnimationCall(calls: any[]) {
16+
return calls.find(call => {
17+
const keyframes = call[0];
18+
if (!Array.isArray(keyframes)) return false;
19+
return keyframes.some(
20+
(kf: any) => kf.opacity === 0 && typeof kf.transform === 'string' && kf.transform.includes('scale'),
21+
);
22+
});
23+
}
24+
25+
describe('Action open animation', () => {
26+
beforeEach(() => {
27+
clearFetchCache();
28+
vi.mocked(Element.prototype.animate).mockClear();
29+
});
30+
31+
it('calls el.animate with add keyframes when "Update profile" action opens', async () => {
32+
const { wrapper } = await createFixtures(f => {
33+
f.withName();
34+
f.withEmailAddress();
35+
f.withUser({
36+
first_name: 'Test',
37+
last_name: 'User',
38+
email_addresses: ['test@clerk.com'],
39+
});
40+
});
41+
42+
const { userEvent } = render(<Account />, { wrapper });
43+
vi.mocked(Element.prototype.animate).mockClear();
44+
45+
await userEvent.click(screen.getByRole('button', { name: /update profile/i }));
46+
await waitFor(() => expect(screen.getByLabelText(/first name/i)).toBeInTheDocument());
47+
48+
expect(findAddAnimationCall(vi.mocked(Element.prototype.animate).mock.calls)).toBeDefined();
49+
});
50+
51+
it('calls el.animate with add keyframes when "Add email" action opens', async () => {
52+
const { wrapper } = await createFixtures(f => {
53+
f.withEmailAddress();
54+
f.withUser({
55+
first_name: 'Test',
56+
last_name: 'User',
57+
email_addresses: ['test@clerk.com'],
58+
});
59+
});
60+
61+
const { userEvent } = render(<Account />, { wrapper });
62+
vi.mocked(Element.prototype.animate).mockClear();
63+
64+
await userEvent.click(screen.getByRole('button', { name: /add email address/i }));
65+
await waitFor(() => expect(screen.getByLabelText(/email address/i)).toBeInTheDocument());
66+
67+
expect(findAddAnimationCall(vi.mocked(Element.prototype.animate).mock.calls)).toBeDefined();
68+
});
69+
70+
it('calls el.animate with add keyframes when "Remove email" action opens via menu', async () => {
71+
const { wrapper } = await createFixtures(f => {
72+
f.withEmailAddress();
73+
f.withUser({
74+
first_name: 'Test',
75+
last_name: 'User',
76+
email_addresses: ['test@clerk.com', 'secondary@clerk.com'],
77+
});
78+
});
79+
80+
const { userEvent } = render(<Account />, { wrapper });
81+
82+
// Open three-dots menu on the secondary (non-primary) email
83+
const menuButtons = screen.getAllByRole('button', { name: /open menu/i });
84+
await userEvent.click(menuButtons[menuButtons.length - 1]);
85+
86+
// Click "Remove" in the dropdown
87+
const removeItem = await screen.findByRole('menuitem', { name: /remove/i });
88+
vi.mocked(Element.prototype.animate).mockClear();
89+
await userEvent.click(removeItem);
90+
91+
// The remove confirmation card should appear
92+
await waitFor(() => expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument());
93+
94+
expect(findAddAnimationCall(vi.mocked(Element.prototype.animate).mock.calls)).toBeDefined();
95+
});
96+
97+
it('composed sections: "Add email" triggers add animation', async () => {
98+
const { wrapper } = await createFixtures(f => {
99+
f.withEmailAddress();
100+
f.withUser({
101+
first_name: 'Test',
102+
last_name: 'User',
103+
email_addresses: ['test@clerk.com'],
104+
});
105+
});
106+
107+
const { userEvent } = render(
108+
<Account>
109+
<AccountProfile />
110+
<AccountEmails />
111+
</Account>,
112+
{ wrapper },
113+
);
114+
115+
vi.mocked(Element.prototype.animate).mockClear();
116+
await userEvent.click(screen.getByRole('button', { name: /add email address/i }));
117+
await waitFor(() => expect(screen.getByLabelText(/email address/i)).toBeInTheDocument());
118+
119+
expect(findAddAnimationCall(vi.mocked(Element.prototype.animate).mock.calls)).toBeDefined();
120+
});
121+
122+
it('composed sections: "Remove email" via menu triggers add animation', async () => {
123+
const { wrapper } = await createFixtures(f => {
124+
f.withEmailAddress();
125+
f.withUser({
126+
first_name: 'Test',
127+
last_name: 'User',
128+
email_addresses: ['test@clerk.com', 'secondary@clerk.com'],
129+
});
130+
});
131+
132+
const { userEvent } = render(
133+
<Account>
134+
<AccountProfile />
135+
<AccountEmails />
136+
</Account>,
137+
{ wrapper },
138+
);
139+
140+
// Verify emails rendered
141+
screen.getByText('test@clerk.com');
142+
screen.getByText('secondary@clerk.com');
143+
144+
// Find and click a menu trigger
145+
const menuButtons = screen.getAllByRole('button', { name: /open menu/i });
146+
expect(menuButtons.length).toBeGreaterThan(0);
147+
await userEvent.click(menuButtons[menuButtons.length - 1]);
148+
149+
// Wait for menu to appear and click remove
150+
const removeItem = await screen.findByRole('menuitem', { name: /remove/i });
151+
vi.mocked(Element.prototype.animate).mockClear();
152+
await userEvent.click(removeItem);
153+
154+
// Wait for the remove confirmation form to appear
155+
await waitFor(
156+
() => {
157+
expect(screen.getByText(/will be removed/i)).toBeInTheDocument();
158+
},
159+
{ timeout: 2000 },
160+
);
161+
162+
expect(findAddAnimationCall(vi.mocked(Element.prototype.animate).mock.calls)).toBeDefined();
163+
});
164+
});

0 commit comments

Comments
 (0)