Compare commits

...

1 Commits

Author SHA1 Message Date
SagarRajput-7
ebaa44c743 fix(settings): disable tabPane animation to prevent stale pane mounts 2026-06-16 15:16:06 +05:30
7 changed files with 127 additions and 2 deletions

View File

@@ -90,4 +90,20 @@ describe('RouteTab component', () => {
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
expect(onChangeHandler).toHaveBeenCalled();
});
it('unmounts inactive tab pane content after switching', () => {
const history = createMemoryHistory({ initialEntries: ['/tab1'] });
render(
<Router history={history}>
<RouteTab history={history} routes={testRoutes} activeKey="Tab1" />
</Router>,
);
expect(screen.getByText('Dummy Component 1')).toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
expect(screen.queryByText('Dummy Component 1')).not.toBeInTheDocument();
expect(screen.getByText('Dummy Component 2')).toBeInTheDocument();
});
});

View File

@@ -59,7 +59,7 @@ function RouteTab({
destroyInactiveTabPane
activeKey={currentRoute?.key || activeKey}
defaultActiveKey={currentRoute?.key || activeKey}
animated
animated={{ inkBar: true, tabPane: false }}
items={items}
tabBarExtraContent={
showRightSection && (

View File

@@ -11,6 +11,7 @@ import CreateAlertV2 from 'container/CreateAlertV2';
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import useReducedMotion from 'hooks/useReducedMotion';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { AlertListTabs } from 'pages/AlertList/types';
@@ -26,6 +27,7 @@ import './CreateAlertRule.styles.scss';
function CreateRules(): JSX.Element {
const [formInstance] = Form.useForm();
const prefersReducedMotion = useReducedMotion();
const compositeQuery = useGetCompositeQueryParam();
const queryParams = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
@@ -41,7 +43,7 @@ function CreateRules(): JSX.Element {
useEffect(() => {
if (isTypeSelectionMode) {
logEvent('Alert: New alert data source selection page visited', {});
void logEvent('Alert: New alert data source selection page visited', {});
}
}, [isTypeSelectionMode]);
@@ -187,6 +189,7 @@ function CreateRules(): JSX.Element {
return (
<Tabs
destroyInactiveTabPane
animated={!prefersReducedMotion}
items={items}
activeKey={AlertListTabs.ALERT_RULES}
onChange={handleTabChange}

View File

@@ -0,0 +1,80 @@
import { act, renderHook } from '@testing-library/react';
import useReducedMotion from 'hooks/useReducedMotion';
type ChangeListener = (e: Partial<MediaQueryListEvent>) => void;
function mockMatchMedia(matches: boolean): {
setMatches: (next: boolean) => void;
} {
const listeners: ChangeListener[] = [];
let currentMatches = matches;
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn(() => ({
get matches() {
return currentMatches;
},
addEventListener: jest.fn((_: string, fn: ChangeListener) => {
listeners.push(fn);
}),
removeEventListener: jest.fn(),
})),
});
return {
setMatches: (next: boolean): void => {
currentMatches = next;
listeners.forEach((fn) => fn({ matches: next } as MediaQueryListEvent));
},
};
}
describe('useReducedMotion', () => {
it('returns false when prefers-reduced-motion is not set', () => {
mockMatchMedia(false);
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(false);
});
it('returns true when prefers-reduced-motion: reduce is active', () => {
mockMatchMedia(true);
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(true);
});
it('updates when system preference changes at runtime', () => {
const { setMatches } = mockMatchMedia(false);
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(false);
act(() => {
setMatches(true);
});
expect(result.current).toBe(true);
act(() => {
setMatches(false);
});
expect(result.current).toBe(false);
});
it('removes event listener on unmount', () => {
const removeEventListener = jest.fn();
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn(() => ({
matches: false,
addEventListener: jest.fn(),
removeEventListener,
})),
});
const { unmount } = renderHook(() => useReducedMotion());
unmount();
expect(removeEventListener).toHaveBeenCalledWith(
'change',
expect.any(Function),
);
});
});

View File

@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react';
function useReducedMotion(): boolean {
const [prefersReducedMotion, setPrefersReducedMotion] = useState<boolean>(
() => window.matchMedia('(prefers-reduced-motion: reduce)').matches,
);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
const onChange = (e: MediaQueryListEvent): void => {
setPrefersReducedMotion(e.matches);
};
mediaQuery.addEventListener('change', onChange);
return (): void => mediaQuery.removeEventListener('change', onChange);
}, []);
return prefersReducedMotion;
}
export default useReducedMotion;

View File

@@ -8,6 +8,7 @@ import AllAlertRules from 'container/ListAlertRules';
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
import RoutingPolicies from 'container/RoutingPolicies';
import TriggeredAlerts from 'container/TriggeredAlerts';
import useReducedMotion from 'hooks/useReducedMotion';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
@@ -21,6 +22,7 @@ function AllAlertList(): JSX.Element {
const urlQuery = useUrlQuery();
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const prefersReducedMotion = useReducedMotion();
const tab = urlQuery.get('tab');
const subTab = urlQuery.get('subTab');
@@ -101,6 +103,7 @@ function AllAlertList(): JSX.Element {
return (
<Tabs
destroyInactiveTabPane
animated={!prefersReducedMotion}
items={items}
activeKey={tab || AlertListTabs.ALERT_RULES}
onChange={(tab): void => {

View File

@@ -5,6 +5,7 @@ import DBCall from 'container/MetricsApplication/Tabs/DBCall';
import External from 'container/MetricsApplication/Tabs/External';
import Overview from 'container/MetricsApplication/Tabs/Overview';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import useReducedMotion from 'hooks/useReducedMotion';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -20,6 +21,7 @@ function MetricsApplication(): JSX.Element {
}>();
const activeKey = useMetricsApplicationTabKey();
const prefersReducedMotion = useReducedMotion();
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
@@ -56,6 +58,7 @@ function MetricsApplication(): JSX.Element {
activeKey={activeKey}
className="service-route-tab"
destroyInactiveTabPane
animated={!prefersReducedMotion}
onChange={onTabChange}
/>
</div>