diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.tsx b/packages/grafana-ui/src/components/Spinner/Spinner.tsx index 6867bdde0c2..a4d45805f29 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.tsx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.tsx @@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes'; import { IconSize, isIconSize } from '../../types'; +import { t } from '../../utils/i18n'; import { Icon } from '../Icon/Icon'; import { getIconRoot, getIconSubDir } from '../Icon/utils'; @@ -38,6 +39,8 @@ export const Spinner = ({ const styles = useStyles2(getStyles); const deprecatedStyles = useStyles2(getDeprecatedStyles, size); + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + const iconName = prefersReducedMotion ? 'hourglass' : 'spinner'; // this entire if statement is handling the deprecated size prop // TODO remove once we fully remove the deprecated type @@ -80,7 +83,17 @@ export const Spinner = ({ className )} > - + ); }; diff --git a/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx index 74b3af55698..20a5eb1db1c 100644 --- a/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx +++ b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx @@ -30,11 +30,14 @@ const renderWithProvider = ({ initialState }: { initialState?: Partial { beforeEach(() => { - (window.matchMedia as jest.Mock).mockImplementation(() => ({ - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - matches: true, - })); + jest.spyOn(window, 'matchMedia').mockImplementation( + () => + ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + matches: true, + }) as unknown as MediaQueryList + ); }); it('should only render if more than one organisations', () => { @@ -80,11 +83,14 @@ describe('OrganisationSwitcher', () => { }); it('should render a picker in mobile screen', () => { - (window.matchMedia as jest.Mock).mockImplementation(() => ({ - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - matches: false, - })); + jest.spyOn(window, 'matchMedia').mockImplementation( + () => + ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + matches: false, + }) as unknown as MediaQueryList + ); renderWithProvider({ initialState: { organization: { diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.test.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.test.tsx index c0888649c4b..d883b0a32c8 100644 --- a/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.test.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.test.tsx @@ -14,11 +14,14 @@ const renderComponent = (options?: { props: TopSearchBarSectionProps }) => { describe('TopSearchBarSection', () => { it('should use a wrapper on non mobile screen', () => { - (window.matchMedia as jest.Mock).mockImplementation(() => ({ - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - matches: true, - })); + jest.spyOn(window, 'matchMedia').mockImplementation( + () => + ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + matches: true, + }) as unknown as MediaQueryList + ); const component = renderComponent(); @@ -27,11 +30,14 @@ describe('TopSearchBarSection', () => { }); it('should not use a wrapper on mobile screen', () => { - (window.matchMedia as jest.Mock).mockImplementation(() => ({ - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - matches: false, - })); + jest.spyOn(window, 'matchMedia').mockImplementation( + () => + ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + matches: false, + }) as unknown as MediaQueryList + ); const component = renderComponent(); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index dcba7b50053..76d9d9fe01a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -618,6 +618,9 @@ "select": { "no-options-label": "No options found", "placeholder": "Choose" + }, + "spinner": { + "aria-label": "Loading" } }, "graph": { diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index deb470d072b..7142d64ccf6 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -618,6 +618,9 @@ "select": { "no-options-label": "Ńő őpŧįőʼnş ƒőūʼnđ", "placeholder": "Cĥőőşę" + }, + "spinner": { + "aria-label": "Ŀőäđįʼnģ" } }, "graph": { diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index 4e77c25a542..4d34bb4a7fd 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -34,19 +34,15 @@ global.grafanaBootData = { navTree: [], }; -// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom -Object.defineProperty(global, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), +window.matchMedia = (query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), }); angular.module('grafana', ['ngRoute']);