Skip to content

Commit 751cb84

Browse files
committed
chore(AnimationsProvider): Add tests
1 parent 49bcb04 commit 751cb84

8 files changed

Lines changed: 286 additions & 1 deletion

File tree

packages/react-core/src/components/Alert/__tests__/AlertGroup.test.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
44
import { Alert } from '../../Alert';
55
import { AlertGroup } from '../../Alert';
66
import { AlertActionCloseButton } from '../../../components/Alert/AlertActionCloseButton';
7+
import { AnimationsProvider } from '../../../helpers/AnimationsProvider';
78

89
test('Alert Group renders without children', () => {
910
render(
@@ -130,3 +131,96 @@ test('Does not call the callback set by updateTransitionEnd when transition ends
130131
fireEvent.transitionEnd(screen.getByText('Test Alert').closest('.pf-v6-c-alert-group__item') as HTMLElement);
131132
expect(mockCallback).toHaveBeenCalledTimes(1);
132133
});
134+
135+
// Animation context tests
136+
test('respects AnimationsProvider context when no local hasAnimations prop', () => {
137+
const mockCallback = jest.fn();
138+
139+
render(
140+
<AnimationsProvider config={{ hasAnimations: true }}>
141+
<AlertGroup isToast appendTo={document.body}>
142+
<Alert
143+
isLiveRegion
144+
title={'Test Alert'}
145+
actionClose={<AlertActionCloseButton aria-label="Close" onClose={mockCallback} />}
146+
/>
147+
</AlertGroup>
148+
</AnimationsProvider>
149+
);
150+
151+
const closeButton = screen.getByLabelText('Close');
152+
fireEvent.click(closeButton);
153+
expect(mockCallback).not.toHaveBeenCalled();
154+
155+
// Should call callback on transition end when animations are enabled via context
156+
fireEvent.transitionEnd(screen.getByText('Test Alert').closest('.pf-v6-c-alert-group__item') as HTMLElement);
157+
expect(mockCallback).toHaveBeenCalled();
158+
});
159+
160+
test('local hasAnimations prop takes precedence over context', () => {
161+
window.matchMedia = (query) => ({
162+
matches: false,
163+
media: query,
164+
onchange: null,
165+
addListener: () => {}, // deprecated
166+
removeListener: () => {}, // deprecated
167+
addEventListener: () => {},
168+
removeEventListener: () => {},
169+
dispatchEvent: () => true
170+
});
171+
172+
const mockCallback = jest.fn();
173+
174+
render(
175+
<AnimationsProvider config={{ hasAnimations: true }}>
176+
<AlertGroup hasAnimations={false} isToast appendTo={document.body}>
177+
<Alert
178+
isLiveRegion
179+
title={'Test Alert'}
180+
actionClose={<AlertActionCloseButton aria-label="Close" onClose={mockCallback} />}
181+
/>
182+
</AlertGroup>
183+
</AnimationsProvider>
184+
);
185+
186+
const closeButton = screen.getByLabelText('Close');
187+
fireEvent.click(closeButton);
188+
expect(mockCallback).toHaveBeenCalledTimes(1);
189+
190+
// Should not call callback again on transition end when local hasAnimations=false overrides context
191+
fireEvent.transitionEnd(screen.getByText('Test Alert').closest('.pf-v6-c-alert-group__item') as HTMLElement);
192+
expect(mockCallback).toHaveBeenCalledTimes(1);
193+
});
194+
195+
test('works without AnimationsProvider (backward compatibility)', () => {
196+
window.matchMedia = (query) => ({
197+
matches: false,
198+
media: query,
199+
onchange: null,
200+
addListener: () => {}, // deprecated
201+
removeListener: () => {}, // deprecated
202+
addEventListener: () => {},
203+
removeEventListener: () => {},
204+
dispatchEvent: () => true
205+
});
206+
207+
const mockCallback = jest.fn();
208+
209+
render(
210+
<AlertGroup isToast appendTo={document.body}>
211+
<Alert
212+
isLiveRegion
213+
title={'Test Alert'}
214+
actionClose={<AlertActionCloseButton aria-label="Close" onClose={mockCallback} />}
215+
/>
216+
</AlertGroup>
217+
);
218+
219+
const closeButton = screen.getByLabelText('Close');
220+
fireEvent.click(closeButton);
221+
expect(mockCallback).toHaveBeenCalledTimes(1);
222+
223+
// Should not call callback again on transition end when no context and no local hasAnimations
224+
fireEvent.transitionEnd(screen.getByText('Test Alert').closest('.pf-v6-c-alert-group__item') as HTMLElement);
225+
expect(mockCallback).toHaveBeenCalledTimes(1);
226+
});

packages/react-core/src/components/DualListSelector/__tests__/DualListSelector.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styles from '@patternfly/react-styles/css/components/DualListSelector/dua
33
import { DualListSelector } from '../DualListSelector';
44
import { DualListSelectorPane } from '../DualListSelectorPane';
55
import { SearchInput } from '../../SearchInput';
6+
import { AnimationsProvider } from '../../../helpers/AnimationsProvider';
67

78
// The following tests can be removed as part of https://github.com/patternfly/patternfly-react/issues/11838
89
describe('Opt-in animations', () => {
@@ -29,6 +30,33 @@ describe('Opt-in animations', () => {
2930

3031
expect(screen.getByTestId('test-id')).toHaveClass(styles.modifiers.animateExpand);
3132
});
33+
34+
// Animation context tests
35+
test('respects AnimationsProvider context when no local hasAnimations prop', () => {
36+
render(
37+
<AnimationsProvider config={{ hasAnimations: true }}>
38+
<DualListSelector isTree data-testid="test-id" />
39+
</AnimationsProvider>
40+
);
41+
42+
expect(screen.getByTestId('test-id')).toHaveClass(styles.modifiers.animateExpand);
43+
});
44+
45+
test('local hasAnimations prop takes precedence over context', () => {
46+
render(
47+
<AnimationsProvider config={{ hasAnimations: true }}>
48+
<DualListSelector isTree hasAnimations={false} data-testid="test-id" />
49+
</AnimationsProvider>
50+
);
51+
52+
expect(screen.getByTestId('test-id')).not.toHaveClass(styles.modifiers.animateExpand);
53+
});
54+
55+
test('works without AnimationsProvider (backward compatibility)', () => {
56+
render(<DualListSelector isTree data-testid="test-id" />);
57+
58+
expect(screen.getByTestId('test-id')).not.toHaveClass(styles.modifiers.animateExpand);
59+
});
3260
});
3361

3462
// Following tests should be moved to a separate DualListSelectorPane test file

packages/react-core/src/components/Form/__tests__/FormFieldGroupExpandable.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render, screen } from '@testing-library/react';
22
import { FormFieldGroupExpandable } from '../FormFieldGroupExpandable';
3+
import { AnimationsProvider } from '../../../helpers/AnimationsProvider';
34
import styles from '@patternfly/react-styles/css/components/Form/form';
45

56
test('Does not render children by default', () => {
@@ -53,3 +54,32 @@ test(`Renders with class ${styles.modifiers.expandable} when hasAnimations is tr
5354

5455
expect(screen.getByRole('group')).toHaveClass(styles.modifiers.expandable);
5556
});
57+
58+
// Regression tests for AnimationsProvider context
59+
test('respects AnimationsProvider context when no local hasAnimations prop', () => {
60+
render(
61+
<AnimationsProvider config={{ hasAnimations: true }}>
62+
<FormFieldGroupExpandable toggleAriaLabel="Toggle label">Child content</FormFieldGroupExpandable>
63+
</AnimationsProvider>
64+
);
65+
66+
expect(screen.getByRole('group')).toHaveClass(styles.modifiers.expandable);
67+
});
68+
69+
test('local hasAnimations prop takes precedence over context', () => {
70+
render(
71+
<AnimationsProvider config={{ hasAnimations: true }}>
72+
<FormFieldGroupExpandable hasAnimations={false} toggleAriaLabel="Toggle label">
73+
Child content
74+
</FormFieldGroupExpandable>
75+
</AnimationsProvider>
76+
);
77+
78+
expect(screen.getByRole('group')).not.toHaveClass(styles.modifiers.expandable);
79+
});
80+
81+
test('works without AnimationsProvider (backward compatibility)', () => {
82+
render(<FormFieldGroupExpandable toggleAriaLabel="Toggle label">Child content</FormFieldGroupExpandable>);
83+
84+
expect(screen.getByRole('group')).not.toHaveClass(styles.modifiers.expandable);
85+
});

packages/react-core/src/components/SearchInput/__tests__/SearchInput.test.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event';
55
import { SearchInput } from '../SearchInput';
66
import { FormGroup } from '../../Form';
77
import { Button } from '../../Button';
8+
import { AnimationsProvider } from '../../../helpers/AnimationsProvider';
89
import ExternalLinkSquareAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-square-alt-icon';
910
import badgeStyles from '@patternfly/react-styles/css/components/Badge/badge';
1011
import textInputGroupStyles from '@patternfly/react-styles/css/components/TextInputGroup/text-input-group';
@@ -363,3 +364,54 @@ test('Additional props are spread when inputProps prop is passed', () => {
363364
render(<SearchInput aria-label="Test input" inputProps={{ autofocus: 'true' }} />);
364365
expect(screen.getByLabelText('Test input')).toHaveAttribute('autofocus', 'true');
365366
});
367+
368+
// Animation context tests
369+
test('respects AnimationsProvider context when no local hasAnimations prop for expandable input', () => {
370+
render(
371+
<AnimationsProvider config={{ hasAnimations: true }}>
372+
<SearchInput
373+
data-testid="test-id"
374+
expandableInput={{
375+
isExpanded: false,
376+
onToggleExpand: () => {},
377+
toggleAriaLabel: 'Test label'
378+
}}
379+
/>
380+
</AnimationsProvider>
381+
);
382+
383+
expect(screen.getByTestId('test-id')).toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`);
384+
});
385+
386+
test('local hasAnimations prop takes precedence over context for expandable input', () => {
387+
render(
388+
<AnimationsProvider config={{ hasAnimations: true }}>
389+
<SearchInput
390+
data-testid="test-id"
391+
expandableInput={{
392+
hasAnimations: false,
393+
isExpanded: false,
394+
onToggleExpand: () => {},
395+
toggleAriaLabel: 'Test label'
396+
}}
397+
/>
398+
</AnimationsProvider>
399+
);
400+
401+
expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`);
402+
});
403+
404+
test('works without AnimationsProvider for expandable input (backward compatibility)', () => {
405+
render(
406+
<SearchInput
407+
data-testid="test-id"
408+
expandableInput={{
409+
isExpanded: false,
410+
onToggleExpand: () => {},
411+
toggleAriaLabel: 'Test label'
412+
}}
413+
/>
414+
);
415+
416+
expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`);
417+
});

packages/react-core/src/components/TreeView/__tests__/TreeView.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { render, screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { TreeView } from '../TreeView';
4+
import { AnimationsProvider } from '../../../helpers/AnimationsProvider';
45

56
jest.mock('../TreeViewList', () => ({
67
TreeViewList: ({
@@ -293,6 +294,33 @@ test('Passes hasAnimations to TreeViewListItem', () => {
293294

294295
expect(screen.getByText('TreeViewListItem hasAnimations: true')).toBeVisible();
295296
});
297+
298+
// Animation context tests
299+
test('respects AnimationsProvider context when no local hasAnimations prop', () => {
300+
render(
301+
<AnimationsProvider config={{ hasAnimations: true }}>
302+
<TreeView data={[basicData]} />
303+
</AnimationsProvider>
304+
);
305+
306+
expect(screen.getByText('TreeViewListItem hasAnimations: true')).toBeVisible();
307+
});
308+
309+
test('local hasAnimations prop takes precedence over context', () => {
310+
render(
311+
<AnimationsProvider config={{ hasAnimations: true }}>
312+
<TreeView data={[basicData]} hasAnimations={false} />
313+
</AnimationsProvider>
314+
);
315+
316+
expect(screen.getByText('TreeViewListItem hasAnimations: false')).toBeVisible();
317+
});
318+
319+
test('works without AnimationsProvider (backward compatibility)', () => {
320+
render(<TreeView data={[basicData]} />);
321+
322+
expect(screen.getByText('TreeViewListItem hasAnimations: false')).toBeVisible();
323+
});
296324
test('Passes data.children to TreeViewListItem', () => {
297325
render(<TreeView data={[{ ...basicData, children: [{ name: 'Child 1' }] }]} />);
298326

packages/react-core/src/helpers/AnimationsProvider/__tests__/AnimationsProvider.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,29 @@ describe('AnimationsProvider', () => {
131131
expect(hasAnimationsComponents[0]).toHaveTextContent('animations-enabled'); // Main uses provider
132132
expect(hasAnimationsComponents[1]).toHaveTextContent('animations-disabled'); // Component override
133133
});
134+
135+
test('handles dynamic context changes', () => {
136+
const TestComponent = ({ hasAnimations }: { hasAnimations: boolean }) => (
137+
<AnimationsProvider config={{ hasAnimations }}>
138+
<TestConfigComponent />
139+
<TestHasAnimationsComponent />
140+
</AnimationsProvider>
141+
);
142+
143+
const { rerender } = render(<TestComponent hasAnimations={true} />);
144+
145+
// Initially animations enabled
146+
expect(screen.getByTestId('config')).toHaveTextContent('animations-enabled');
147+
expect(screen.getByTestId('has-animations')).toHaveTextContent('animations-enabled');
148+
149+
// Change to disabled
150+
rerender(<TestComponent hasAnimations={false} />);
151+
expect(screen.getByTestId('config')).toHaveTextContent('animations-disabled');
152+
expect(screen.getByTestId('has-animations')).toHaveTextContent('animations-disabled');
153+
154+
// Change back to enabled
155+
rerender(<TestComponent hasAnimations={true} />);
156+
expect(screen.getByTestId('config')).toHaveTextContent('animations-enabled');
157+
expect(screen.getByTestId('has-animations')).toHaveTextContent('animations-enabled');
158+
});
134159
});

packages/react-table/src/components/Table/__tests__/Table.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
22
import { Table } from '../Table';
33
import { Td } from '../Td';
44
import { Th } from '../Th';
5+
import { AnimationsProvider, useAnimationsConfig, useHasAnimations } from '@patternfly/react-core/dist/esm/helpers';
56
import styles from '@patternfly/react-styles/css/components/Table/table';
67

78
test('Renders without children', () => {
@@ -57,6 +58,33 @@ test(`Renders with class ${styles.modifiers.animateExpand} hasAnimations is true
5758
expect(screen.getByRole('grid', { name: 'Test table' })).toHaveClass(styles.modifiers.animateExpand);
5859
});
5960

61+
// Animation context tests for expandable tables
62+
test('respects AnimationsProvider context when no local hasAnimations prop for expandable table', () => {
63+
render(
64+
<AnimationsProvider config={{ hasAnimations: true }}>
65+
<Table isExpandable aria-label="Test table" />
66+
</AnimationsProvider>
67+
);
68+
69+
expect(screen.getByRole('grid', { name: 'Test table' })).toHaveClass(styles.modifiers.animateExpand);
70+
});
71+
72+
test('local hasAnimations prop takes precedence over context for expandable table', () => {
73+
render(
74+
<AnimationsProvider config={{ hasAnimations: true }}>
75+
<Table isExpandable hasAnimations={false} aria-label="Test table" />
76+
</AnimationsProvider>
77+
);
78+
79+
expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.animateExpand);
80+
});
81+
82+
test('works without AnimationsProvider (backward compatibility) for expandable table', () => {
83+
render(<Table isExpandable aria-label="Test table" />);
84+
85+
expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.animateExpand);
86+
});
87+
6088
test('Matches snapshot without children', () => {
6189
const { asFragment } = render(<Table />);
6290

packages/react-table/src/components/Table/__tests__/__snapshots__/Table.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`Matches snapshot without children 1`] = `
44
<DocumentFragment>
55
<table
66
class="pf-v6-c-table pf-m-grid-md"
7-
data-ouia-component-id="OUIA-Generated-Table-9"
7+
data-ouia-component-id="OUIA-Generated-Table-12"
88
data-ouia-component-type="PF6/Table"
99
data-ouia-safe="true"
1010
role="grid"

0 commit comments

Comments
 (0)