| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import { |
| render, |
| screen, |
| userEvent, |
| waitFor, |
| within, |
| } from 'spec/helpers/testing-library'; |
| import { ThemeMode } from '@superset-ui/core'; |
| import { Menu } from '@superset-ui/core/components'; |
| import { ThemeSubMenuProps, useThemeMenuItems } from './useThemeMenuItems'; |
| |
| // Mock the translation function |
| jest.mock('@superset-ui/core', () => ({ |
| ...jest.requireActual('@superset-ui/core'), |
| t: (key: string) => key, |
| })); |
| |
| const TestComponent = (props: ThemeSubMenuProps) => { |
| const menuItem = useThemeMenuItems(props); |
| return <Menu items={[menuItem]} />; |
| }; |
| |
| // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks |
| describe('useThemeMenuItems', () => { |
| const defaultProps = { |
| allowOSPreference: true, |
| setThemeMode: jest.fn(), |
| themeMode: ThemeMode.DEFAULT, |
| hasLocalOverride: false, |
| onClearLocalSettings: jest.fn(), |
| }; |
| |
| const renderThemeMenu = (props = defaultProps) => |
| render(<TestComponent {...props} />); |
| |
| const findMenuWithText = async (text: string) => { |
| await waitFor(() => { |
| const found = screen |
| .getAllByRole('menu') |
| .some(m => within(m).queryByText(text)); |
| |
| if (!found) throw new Error(`Menu with text "${text}" not yet rendered`); |
| }); |
| |
| return screen.getAllByRole('menu').find(m => within(m).queryByText(text))!; |
| }; |
| |
| beforeEach(() => { |
| jest.clearAllMocks(); |
| }); |
| |
| test('renders Light and Dark theme options by default', async () => { |
| renderThemeMenu(); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Light'); |
| |
| expect(within(menu!).getByText('Light')).toBeInTheDocument(); |
| expect(within(menu!).getByText('Dark')).toBeInTheDocument(); |
| }); |
| |
| test('does not render Match system option when allowOSPreference is false', async () => { |
| renderThemeMenu({ ...defaultProps, allowOSPreference: false }); |
| userEvent.hover(await screen.findByRole('menuitem')); |
| |
| await waitFor(() => { |
| expect(screen.queryByText('Match system')).not.toBeInTheDocument(); |
| }); |
| }); |
| |
| test('renders with allowOSPreference as true by default', async () => { |
| renderThemeMenu(); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Match system'); |
| |
| expect(within(menu).getByText('Match system')).toBeInTheDocument(); |
| }); |
| |
| test('renders clear option when both hasLocalOverride and onClearLocalSettings are provided', async () => { |
| const mockClear = jest.fn(); |
| renderThemeMenu({ |
| ...defaultProps, |
| hasLocalOverride: true, |
| onClearLocalSettings: mockClear, |
| }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Clear local theme'); |
| |
| expect(within(menu).getByText('Clear local theme')).toBeInTheDocument(); |
| }); |
| |
| test('does not render clear option when hasLocalOverride is false', async () => { |
| const mockClear = jest.fn(); |
| renderThemeMenu({ |
| ...defaultProps, |
| hasLocalOverride: false, |
| onClearLocalSettings: mockClear, |
| }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| |
| await waitFor(() => { |
| expect(screen.queryByText('Clear local theme')).not.toBeInTheDocument(); |
| }); |
| }); |
| |
| test('calls setThemeMode with DEFAULT when Light is clicked', async () => { |
| const mockSet = jest.fn(); |
| renderThemeMenu({ ...defaultProps, setThemeMode: mockSet }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Light'); |
| userEvent.click(within(menu).getByText('Light')); |
| |
| expect(mockSet).toHaveBeenCalledWith(ThemeMode.DEFAULT); |
| }); |
| |
| test('calls setThemeMode with DARK when Dark is clicked', async () => { |
| const mockSet = jest.fn(); |
| renderThemeMenu({ ...defaultProps, setThemeMode: mockSet }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Dark'); |
| userEvent.click(within(menu).getByText('Dark')); |
| |
| expect(mockSet).toHaveBeenCalledWith(ThemeMode.DARK); |
| }); |
| |
| test('calls setThemeMode with SYSTEM when Match system is clicked', async () => { |
| const mockSet = jest.fn(); |
| renderThemeMenu({ ...defaultProps, setThemeMode: mockSet }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Match system'); |
| userEvent.click(within(menu).getByText('Match system')); |
| |
| expect(mockSet).toHaveBeenCalledWith(ThemeMode.SYSTEM); |
| }); |
| |
| test('calls onClearLocalSettings when Clear local theme is clicked', async () => { |
| const mockClear = jest.fn(); |
| renderThemeMenu({ |
| ...defaultProps, |
| hasLocalOverride: true, |
| onClearLocalSettings: mockClear, |
| }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Clear local theme'); |
| userEvent.click(within(menu).getByText('Clear local theme')); |
| |
| expect(mockClear).toHaveBeenCalledTimes(1); |
| }); |
| |
| test('displays sun icon for DEFAULT theme', () => { |
| renderThemeMenu({ ...defaultProps, themeMode: ThemeMode.DEFAULT }); |
| expect(screen.getByTestId('sun')).toBeInTheDocument(); |
| }); |
| |
| test('displays moon icon for DARK theme', () => { |
| renderThemeMenu({ ...defaultProps, themeMode: ThemeMode.DARK }); |
| expect(screen.getByTestId('moon')).toBeInTheDocument(); |
| }); |
| |
| test('displays format-painter icon for SYSTEM theme', () => { |
| renderThemeMenu({ ...defaultProps, themeMode: ThemeMode.SYSTEM }); |
| expect(screen.getByTestId('format-painter')).toBeInTheDocument(); |
| }); |
| |
| test('displays override icon when hasLocalOverride is true', () => { |
| renderThemeMenu({ ...defaultProps, hasLocalOverride: true }); |
| expect(screen.getByTestId('thunderbolt')).toBeInTheDocument(); |
| }); |
| |
| test('renders Theme group header', async () => { |
| renderThemeMenu(); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Theme'); |
| |
| expect(within(menu).getByText('Theme')).toBeInTheDocument(); |
| }); |
| |
| test('renders sun icon for Light theme option', async () => { |
| renderThemeMenu(); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Light'); |
| const lightOption = within(menu).getByText('Light').closest('li'); |
| |
| expect(within(lightOption!).getByTestId('sun')).toBeInTheDocument(); |
| }); |
| |
| test('renders moon icon for Dark theme option', async () => { |
| renderThemeMenu(); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Dark'); |
| const darkOption = within(menu).getByText('Dark').closest('li'); |
| |
| expect(within(darkOption!).getByTestId('moon')).toBeInTheDocument(); |
| }); |
| |
| test('renders format-painter icon for Match system option', async () => { |
| renderThemeMenu({ ...defaultProps, allowOSPreference: true }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Match system'); |
| const matchOption = within(menu).getByText('Match system').closest('li'); |
| |
| expect( |
| within(matchOption!).getByTestId('format-painter'), |
| ).toBeInTheDocument(); |
| }); |
| |
| test('renders clear icon for Clear local theme option', async () => { |
| renderThemeMenu({ |
| ...defaultProps, |
| hasLocalOverride: true, |
| onClearLocalSettings: jest.fn(), |
| }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const menu = await findMenuWithText('Clear local theme'); |
| const clearOption = within(menu) |
| .getByText('Clear local theme') |
| .closest('li'); |
| |
| expect(within(clearOption!).getByTestId('clear')).toBeInTheDocument(); |
| }); |
| |
| test('renders divider before clear option when clear option is present', async () => { |
| renderThemeMenu({ |
| ...defaultProps, |
| hasLocalOverride: true, |
| onClearLocalSettings: jest.fn(), |
| }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| |
| const menu = await findMenuWithText('Clear local theme'); |
| const divider = within(menu).queryByRole('separator'); |
| |
| expect(divider).toBeInTheDocument(); |
| }); |
| |
| test('does not render divider when clear option is not present', async () => { |
| renderThemeMenu({ ...defaultProps }); |
| |
| userEvent.hover(await screen.findByRole('menuitem')); |
| const divider = document.querySelector('.ant-menu-item-divider'); |
| |
| expect(divider).toBeNull(); |
| }); |
| }); |