diff --git a/src/react/hooks/paragon/useParagonTheme.test.js b/src/react/hooks/paragon/useParagonTheme.test.js new file mode 100644 index 000000000..93027f62c --- /dev/null +++ b/src/react/hooks/paragon/useParagonTheme.test.js @@ -0,0 +1,91 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useParagonTheme from './useParagonTheme'; +import { getConfig } from '../../../config'; + +jest.mock('../../../config', () => ({ + ...jest.requireActual('.../../../config'), + getConfig: jest.fn().mockReturnValue({ + PARAGON_THEME_URLS: { + core: { + urls: { + default: 'core.css', + }, + }, + defaults: { + light: 'light', + dark: 'dark', + }, + variants: { + light: { + urls: { + default: 'https://cdn.jsdelivr.net/npm/@edx/paragon@$21.0.0/dist/light.min.css', + }, + }, + dark: { + urls: { + default: 'https://cdn.jsdelivr.net/npm/@edx/paragon@$21.0.0/dist/dark.min.css', + }, + }, + }, + }, + }), +})); + +let mockMediaQueryListEvent; +const mockAddEventListener = jest.fn((dispatch, fn) => fn(mockMediaQueryListEvent)); +const mockRemoveEventListener = jest.fn(); + +Object.defineProperty(window, 'matchMedia', { + value: jest.fn(() => ({ + addEventListener: mockAddEventListener, + removeEventListener: mockRemoveEventListener, + })), +}); + +Object.defineProperty(window, 'localStorage', { + value: { + getItem: jest.fn(), + }, +}); + +describe('useParagonTheme', () => { + beforeEach(() => { + document.head.innerHTML = ''; + mockMediaQueryListEvent = { matches: true }; + mockAddEventListener.mockClear(); + mockRemoveEventListener.mockClear(); + window.localStorage.getItem.mockClear(); + }); + + it('should configure theme variants according with system preference and add the change event listener', () => { + const { unmount } = renderHook(() => useParagonTheme(getConfig())); + const themeLinks = document.head.querySelectorAll('link'); + const darkLink = document.head.querySelector('link[data-paragon-theme-variant="dark"]'); + const lightLink = document.head.querySelector('link[data-paragon-theme-variant="light"]'); + act(() => { themeLinks.forEach((link) => link.onload()); }); + + expect(window.matchMedia).toHaveBeenCalledWith('(prefers-color-scheme: dark)'); + expect(mockAddEventListener).toHaveBeenCalled(); + expect(darkLink.rel).toBe('stylesheet'); + expect(lightLink.rel).toBe('alternate stylesheet'); + unmount(); + expect(mockRemoveEventListener).toHaveBeenCalled(); + }); + + it('should configure theme variants according with user preference if is defined (localStorage)', () => { + window.localStorage.getItem.mockReturnValue('light'); + const { unmount } = renderHook(() => useParagonTheme(getConfig())); + const themeLinks = document.head.querySelectorAll('link'); + const darkLink = document.head.querySelector('link[data-paragon-theme-variant="dark"]'); + const lightLink = document.head.querySelector('link[data-paragon-theme-variant="light"]'); + act(() => { themeLinks.forEach((link) => link.onload()); }); + + expect(window.matchMedia).toHaveBeenCalledWith('(prefers-color-scheme: dark)'); + expect(mockAddEventListener).toHaveBeenCalled(); + + expect(darkLink.rel).toBe('alternate stylesheet'); + expect(lightLink.rel).toBe('stylesheet'); + unmount(); + expect(mockRemoveEventListener).toHaveBeenCalled(); + }); +}); diff --git a/src/react/hooks/paragon/useParagonThemeVariants.test.js b/src/react/hooks/paragon/useParagonThemeVariants.test.js index d43dd5588..505b863b4 100644 --- a/src/react/hooks/paragon/useParagonThemeVariants.test.js +++ b/src/react/hooks/paragon/useParagonThemeVariants.test.js @@ -5,18 +5,6 @@ import useParagonThemeVariants from './useParagonThemeVariants'; jest.mock('../../../logging'); -const mockAddEventListener = jest.fn(); -const mockRemoveEventListener = jest.fn(); -const mockOnChange = jest.fn(); - -Object.defineProperty(window, 'matchMedia', { - value: jest.fn(() => ({ - addEventListener: mockAddEventListener, - removeEventListener: mockRemoveEventListener, - onchange: mockOnChange, - })), -}); - describe('useParagonThemeVariants', () => { const themeOnLoad = jest.fn(); @@ -85,34 +73,6 @@ describe('useParagonThemeVariants', () => { expect(document.querySelector('link').href).toBe(`${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.variants.light.fileName}`); }); - it('should configure theme variants according with system preference and add the change event listener', () => { - window.matchMedia['prefers-color-scheme'] = 'dark'; - - const themeVariants = { - light: { - urls: { - default: 'https://cdn.jsdelivr.net/npm/@edx/paragon@$21.0.0/dist/light.min.css', - brandOverride: 'https://cdn.jsdelivr.net/npm/@edx/brand@$2.0.0/dist/light.min.css', - }, - }, - dark: { - urls: { - default: 'https://cdn.jsdelivr.net/npm/@edx/paragon@$21.0.0/dist/dark.min.css', - brandOverride: 'https://cdn.jsdelivr.net/npm/@edx/brand@$2.0.0/dist/dark.min.css', - }, - }, - }; - - const currentThemeVariant = 'light'; - - renderHook(() => useParagonThemeVariants({ themeVariants, currentThemeVariant, onLoad: themeOnLoad })); - - const themeLinks = document.head.querySelectorAll('link'); - act(() => { themeLinks.forEach((link) => link.onload()); }); - - expect(mockAddEventListener).toHaveBeenCalledTimes(1); - }); - it('should do nothing if themeVariants is not configured', () => { const themeVariants = null; const currentTheme = 'light';