blob: 12d6f11360059526fe68281048868c79f398eff7 [file] [log] [blame]
/**
* 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, waitFor } from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
import * as hooks from 'src/views/CRUD/hooks';
import * as themeApi from 'src/features/themes/api';
import * as getBootstrapData from 'src/utils/getBootstrapData';
import ThemesList from './index';
// Mock the getBootstrapData function
jest.mock('src/utils/getBootstrapData', () => ({
__esModule: true,
default: () => ({
common: {
theme: {
enableUiThemeAdministration: true,
},
},
user: {
userId: 1,
username: 'admin',
roles: {
Admin: [['can_write', 'Theme']],
},
},
}),
}));
// Mock theme API functions
jest.mock('src/features/themes/api', () => ({
setSystemDefaultTheme: jest.fn(() => Promise.resolve()),
setSystemDarkTheme: jest.fn(() => Promise.resolve()),
unsetSystemDefaultTheme: jest.fn(() => Promise.resolve()),
unsetSystemDarkTheme: jest.fn(() => Promise.resolve()),
}));
// Mock the CRUD hooks
jest.mock('src/views/CRUD/hooks', () => ({
...jest.requireActual('src/views/CRUD/hooks'),
useListViewResource: jest.fn(),
}));
// Mock the useThemeContext hook
jest.mock('src/theme/ThemeProvider', () => ({
...jest.requireActual('src/theme/ThemeProvider'),
useThemeContext: jest.fn().mockReturnValue({
getCurrentCrudThemeId: jest.fn().mockReturnValue('1'),
appliedTheme: { theme_name: 'Light Theme', id: 1 },
}),
}));
const mockThemes = [
{
id: 1,
theme_name: 'Light Theme',
is_system_default: true,
is_system_dark: false,
created_by: { id: 1, first_name: 'Admin', last_name: 'User' },
changed_on_delta_humanized: '1 day ago',
},
{
id: 2,
theme_name: 'Dark Theme',
is_system_default: false,
is_system_dark: true,
created_by: { id: 1, first_name: 'Admin', last_name: 'User' },
changed_on_delta_humanized: '2 days ago',
},
{
id: 3,
theme_name: 'Custom Theme',
is_system_default: false,
is_system_dark: false,
created_by: { id: 2, first_name: 'Test', last_name: 'User' },
changed_on_delta_humanized: '3 days ago',
},
];
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('ThemesList', () => {
beforeEach(() => {
// Mock the useListViewResource hook
(hooks.useListViewResource as jest.Mock).mockReturnValue({
state: {
loading: false,
resourceCollection: mockThemes,
resourceCount: 3,
bulkSelectEnabled: false,
},
setResourceCollection: jest.fn(),
hasPerm: jest.fn().mockReturnValue(true),
refreshData: jest.fn(),
fetchData: jest.fn(),
toggleBulkSelect: jest.fn(),
});
fetchMock.reset();
fetchMock.get('glob:*/api/v1/theme/*', {
count: 3,
result: mockThemes,
});
});
afterEach(() => {
fetchMock.restore();
jest.clearAllMocks();
});
test('renders the themes list with proper structure', async () => {
render(
<ThemesList addDangerToast={jest.fn()} addSuccessToast={jest.fn()} />,
{
useRedux: true,
useDnd: true,
useQueryParams: true,
},
);
await waitFor(() => {
expect(screen.getByText('Light Theme')).toBeInTheDocument();
expect(screen.getByText('Dark Theme')).toBeInTheDocument();
expect(screen.getByText('Custom Theme')).toBeInTheDocument();
});
});
test('shows system theme badges for default and dark themes', async () => {
render(
<ThemesList addDangerToast={jest.fn()} addSuccessToast={jest.fn()} />,
{
useRedux: true,
useDnd: true,
useQueryParams: true,
},
);
await waitFor(() => {
// Check for system badges
expect(screen.getByText('Light Theme')).toBeInTheDocument();
expect(screen.getByText('Dark Theme')).toBeInTheDocument();
});
// Verify that themes with system flags are marked appropriately
expect(mockThemes[0].is_system_default).toBe(true);
expect(mockThemes[1].is_system_dark).toBe(true);
expect(mockThemes[2].is_system_default).toBe(false);
expect(mockThemes[2].is_system_dark).toBe(false);
});
test('uses flat theme structure for enableUiThemeAdministration', () => {
// Verify the component accesses the correct bootstrap data structure
const { common } = getBootstrapData.default();
// Should access flat structure
expect(common.theme.enableUiThemeAdministration).toBe(true);
// Should NOT have nested settings structure
expect((common.theme as any).settings).toBeUndefined();
});
test('shows admin controls when user has proper permissions', async () => {
render(
<ThemesList addDangerToast={jest.fn()} addSuccessToast={jest.fn()} />,
{
useRedux: true,
useDnd: true,
useQueryParams: true,
},
);
await waitFor(() => {
expect(screen.getByText('Light Theme')).toBeInTheDocument();
});
// Admin controls should be visible
const adminElements = screen.queryAllByRole('button');
expect(adminElements.length).toBeGreaterThan(0);
});
test('calls setSystemDefaultTheme API when setting a theme as default', async () => {
const { setSystemDefaultTheme } = themeApi;
const addSuccessToast = jest.fn();
const refreshData = jest.fn();
(hooks.useListViewResource as jest.Mock).mockReturnValue({
state: {
loading: false,
resourceCollection: mockThemes,
resourceCount: 3,
bulkSelectEnabled: false,
},
setResourceCollection: jest.fn(),
hasPerm: jest.fn().mockReturnValue(true),
refreshData,
fetchData: jest.fn(),
toggleBulkSelect: jest.fn(),
});
render(
<ThemesList
addDangerToast={jest.fn()}
addSuccessToast={addSuccessToast}
/>,
{
useRedux: true,
useDnd: true,
useQueryParams: true,
},
);
await waitFor(() => {
expect(screen.getByText('Custom Theme')).toBeInTheDocument();
});
// Since the actual UI interaction would be complex to test,
// we verify the API function exists and can be called
expect(setSystemDefaultTheme).toBeDefined();
// Test calling the API directly
await setSystemDefaultTheme(3);
expect(setSystemDefaultTheme).toHaveBeenCalledWith(3);
});
test('configures theme deletion endpoint', async () => {
const addDangerToast = jest.fn();
const addSuccessToast = jest.fn();
const refreshData = jest.fn();
// Setup delete mock before rendering
fetchMock.delete('glob:*/api/v1/theme/*', 200);
// Mock the useListViewResource hook with refreshData
(hooks.useListViewResource as jest.Mock).mockReturnValue({
state: {
loading: false,
resourceCollection: mockThemes,
resourceCount: 3,
bulkSelectEnabled: false,
},
setResourceCollection: jest.fn(),
hasPerm: jest.fn().mockReturnValue(true),
refreshData,
fetchData: jest.fn(),
toggleBulkSelect: jest.fn(),
});
render(
<ThemesList
addDangerToast={addDangerToast}
addSuccessToast={addSuccessToast}
/>,
{
useRedux: true,
useDnd: true,
useQueryParams: true,
},
);
await waitFor(() => {
expect(screen.getByText('Custom Theme')).toBeInTheDocument();
});
// Verify that themes are rendered correctly
expect(screen.getByText('Light Theme')).toBeInTheDocument();
expect(screen.getByText('Dark Theme')).toBeInTheDocument();
expect(screen.getByText('Custom Theme')).toBeInTheDocument();
// Verify that action buttons are present (which include delete for non-system themes)
const actionButtons = screen.getAllByRole('button');
expect(actionButtons.length).toBeGreaterThan(0);
});
});