// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. 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 React from 'react';
import '@testing-library/jest-dom';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Overview from './OverviewTab';
import Examples from './Examples';
import Analytics from './Analytics';
import {
INSTALL_APP_EXAMPLES_API_URL,
INSTALL_AVAILABLE_EXAMPLES_API_URL,
USAGE_ANALYTICS_API_URL
} from '../Components/utils';
import { get, post, sendApiRequest } from '../../../api/utils';
import * as hueConfigModule from '../../../config/hueConfig';
import huePubSub from '../../../utils/huePubSub';
jest.mock('../../../api/utils', () => ({
post: jest.fn(),
get: jest.fn(),
sendApiRequest: jest.fn(),
HttpMethod: {
POST: 'post',
PUT: 'put',
PATCH: 'patch'
}
}));
jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
);
jest.mock('../../../config/hueConfig', () => ({
getLastKnownConfig: jest.fn()
}));
jest.mock('../../../utils/huePubSub', () => ({
publish: jest.fn()
}));
describe('OverviewTab', () => {
beforeEach(() => {
(hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({
hue_config: { is_admin: true }
});
});
afterEach(() => {
jest.clearAllMocks();
});
it('renders the Tabs with the correct tab labels', () => {
render();
expect(screen.getByText('Config Status')).toBeInTheDocument();
expect(screen.getByText('Examples')).toBeInTheDocument();
expect(screen.getByText('Analytics')).toBeInTheDocument();
});
describe('Examples component', () => {
const availableAppsResponse = {
apps: {
hive: 'Hive',
impala: 'Impala',
search: 'Solr Search'
}
};
beforeEach(() => {
(get as jest.Mock).mockImplementation(url => {
if (url === INSTALL_AVAILABLE_EXAMPLES_API_URL) {
return Promise.resolve(availableAppsResponse);
}
return Promise.reject();
});
(post as jest.Mock).mockImplementation(() =>
Promise.resolve({ status: 0, message: 'Success' })
);
});
it('fetch and display available apps', async () => {
render();
expect(get).toHaveBeenCalledWith(INSTALL_AVAILABLE_EXAMPLES_API_URL, {});
for (const [, appName] of Object.entries(availableAppsResponse.apps)) {
expect(await screen.findByText(appName)).toBeInTheDocument();
}
});
it('post call to install apps without data like hive when the install button is clicked', async () => {
render();
const installButton = await screen.findByText('Hive');
await userEvent.click(installButton);
expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, { app_name: 'hive' });
});
it('post call to install Solr Search example and its data when the install button is clicked', async () => {
render();
const solrData = ['log_analytics_demo', 'twitter_demo', 'yelp_demo'];
const installButton = await screen.findByText('Solr Search');
await userEvent.click(installButton);
for (const dataEntry of solrData) {
expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, {
app_name: 'search',
data: dataEntry
});
}
expect(post).toHaveBeenCalledTimes(solrData.length);
});
});
describe('Analytics component', () => {
const mockAnalyticsData = { collectUsage: true };
const mockAnalyticsDataDisabled = { collectUsage: false };
const expectedFetchOptions = {
silenceErrors: true,
ignoreSuccessErrors: true
};
const expectedOptions = {
silenceErrors: true,
ignoreSuccessErrors: true,
qsEncodeData: false
};
const renderAnalyticsAndWaitForLoad = async () => {
render();
await waitFor(() => {
expect(get).toHaveBeenCalledWith(USAGE_ANALYTICS_API_URL, undefined, expectedFetchOptions);
});
};
const getCheckboxElement = () => screen.getByRole('checkbox') as HTMLInputElement;
const expectCheckboxState = async (checked: boolean) => {
const checkbox = getCheckboxElement();
await waitFor(() => {
expect(checkbox.checked).toBe(checked);
});
return checkbox;
};
beforeEach(() => {
(get as jest.Mock).mockImplementation(url => {
if (url === USAGE_ANALYTICS_API_URL) {
return Promise.resolve(mockAnalyticsData);
}
return Promise.reject();
});
(sendApiRequest as jest.Mock).mockImplementation(() =>
Promise.resolve({ collect_usage: true })
);
});
afterEach(() => {
jest.clearAllMocks();
});
it('renders analytics title and checkbox', async () => {
await renderAnalyticsAndWaitForLoad();
expect(screen.getByText('Anonymous usage analytics')).toBeInTheDocument();
expect(
screen.getByLabelText('Help improve Hue with anonymous usage analytics.')
).toBeInTheDocument();
expect(screen.getByRole('checkbox')).toBeInTheDocument();
});
it('fetches and displays analytics data on mount', async () => {
await renderAnalyticsAndWaitForLoad();
await expectCheckboxState(true);
});
it('checkbox reflects analytics data state when disabled', async () => {
(get as jest.Mock).mockResolvedValue(mockAnalyticsDataDisabled);
await renderAnalyticsAndWaitForLoad();
await expectCheckboxState(false);
});
it('shows loading state during initial data fetch', () => {
(get as jest.Mock).mockImplementation(() => new Promise(() => {})); // Never resolves
render();
expect(screen.getAllByTestId('loading-error-wrapper__spinner')[0]).toBeInTheDocument();
});
it('shows error message when data fetch fails', async () => {
const errorMessage = 'Failed to fetch analytics data';
(get as jest.Mock).mockRejectedValue(errorMessage);
render();
await waitFor(() => {
expect(screen.getByTestId('loading-error-wrapper__errors')).toBeInTheDocument();
});
});
it('handles checkbox toggle to enable analytics', async () => {
(get as jest.Mock).mockResolvedValue(mockAnalyticsDataDisabled);
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(false);
await userEvent.click(checkbox);
await waitFor(() => {
expect(sendApiRequest).toHaveBeenCalledWith(
'put',
USAGE_ANALYTICS_API_URL,
{ collect_usage: true },
expectedOptions
);
});
});
it('handles checkbox toggle to disable analytics', async () => {
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
expect(sendApiRequest).toHaveBeenCalledWith(
'put',
USAGE_ANALYTICS_API_URL,
{ collect_usage: false },
expectedOptions
);
});
});
it('disables checkbox during save operation', async () => {
(sendApiRequest as jest.Mock).mockImplementation(() => new Promise(() => {})); // Never resolves
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
const currentCheckbox = getCheckboxElement();
expect(currentCheckbox.disabled).toBe(true);
});
});
it('shows loading state during save operation', async () => {
(sendApiRequest as jest.Mock).mockImplementation(() => new Promise(() => {}));
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
expect(screen.getAllByTestId('loading-error-wrapper__spinner')[0]).toBeInTheDocument();
});
});
it('shows error message when save operation fails', async () => {
const errorMessage = 'Failed to update analytics preference';
(sendApiRequest as jest.Mock).mockRejectedValue(errorMessage);
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
expect(screen.getByTestId('loading-error-wrapper__errors')).toBeInTheDocument();
});
});
it('publishes success message when analytics are enabled', async () => {
(get as jest.Mock).mockResolvedValue(mockAnalyticsDataDisabled);
(sendApiRequest as jest.Mock).mockResolvedValue({ collect_usage: true });
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(false);
await userEvent.click(checkbox);
await waitFor(() => {
expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', {
message: 'Analytics have been activated.'
});
});
});
it('publishes success message when analytics are disabled', async () => {
(sendApiRequest as jest.Mock).mockResolvedValue({ collect_usage: false });
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', {
message: 'Analytics have been deactivated.'
});
});
});
it('reloads data after successful save', async () => {
(sendApiRequest as jest.Mock).mockResolvedValue({ collect_usage: false });
await renderAnalyticsAndWaitForLoad();
const checkbox = await expectCheckboxState(true);
await userEvent.click(checkbox);
await waitFor(() => {
expect(get).toHaveBeenCalledTimes(2);
expect(get).toHaveBeenCalledWith(USAGE_ANALYTICS_API_URL, undefined, expectedFetchOptions);
});
});
it('handles undefined analytics data gracefully', async () => {
(get as jest.Mock).mockResolvedValue({});
await renderAnalyticsAndWaitForLoad();
await expectCheckboxState(false);
});
it('handles multiple error states simultaneously', async () => {
const fetchError = 'Fetch failed';
const saveError = 'Save failed';
(get as jest.Mock).mockRejectedValue(fetchError);
(sendApiRequest as jest.Mock).mockRejectedValue(saveError);
render();
await waitFor(() => {
expect(screen.getByTestId('loading-error-wrapper__errors')).toBeInTheDocument();
});
const checkbox = getCheckboxElement();
await userEvent.click(checkbox);
await waitFor(() => {
expect(screen.getByTestId('loading-error-wrapper__errors')).toBeInTheDocument();
});
});
});
});