mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-20 17:19:08 +08:00
feat: added tests for preferences framework alongside some minor bugs improvements
This commit is contained in:
parent
6f5c4bf904
commit
34cd2c1b40
@ -0,0 +1,150 @@
|
|||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { FormattingOptions, Preferences } from 'providers/preferences/types';
|
||||||
|
import { MemoryRouter, Route, Switch } from 'react-router-dom';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PreferenceContextProvider,
|
||||||
|
usePreferenceContext,
|
||||||
|
} from '../context/PreferenceContextProvider';
|
||||||
|
|
||||||
|
// Mock the usePreferenceSync hook
|
||||||
|
jest.mock('../sync/usePreferenceSync', () => ({
|
||||||
|
usePreferenceSync: jest.fn().mockReturnValue({
|
||||||
|
preferences: {
|
||||||
|
columns: [] as BaseAutocompleteData[],
|
||||||
|
formatting: {
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table',
|
||||||
|
fontSize: 'small',
|
||||||
|
version: 1,
|
||||||
|
} as FormattingOptions,
|
||||||
|
} as Preferences,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
updateColumns: jest.fn(),
|
||||||
|
updateFormatting: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Test component that consumes the context
|
||||||
|
function TestConsumer(): JSX.Element {
|
||||||
|
const context = usePreferenceContext();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div data-testid="mode">{context.mode}</div>
|
||||||
|
<div data-testid="dataSource">{context.dataSource}</div>
|
||||||
|
<div data-testid="loading">{String(context.loading)}</div>
|
||||||
|
<div data-testid="error">{String(context.error)}</div>
|
||||||
|
<div data-testid="savedViewId">{context.savedViewId || 'no-view-id'}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PreferenceContextProvider', () => {
|
||||||
|
it('should provide context with direct mode when no viewKey is present', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={['/logs']}>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path="/logs"
|
||||||
|
component={(): JSX.Element => (
|
||||||
|
<PreferenceContextProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</PreferenceContextProvider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('mode')).toHaveTextContent('direct');
|
||||||
|
expect(screen.getByTestId('dataSource')).toHaveTextContent('logs');
|
||||||
|
expect(screen.getByTestId('loading')).toHaveTextContent('false');
|
||||||
|
expect(screen.getByTestId('error')).toHaveTextContent('null');
|
||||||
|
expect(screen.getByTestId('savedViewId')).toHaveTextContent('no-view-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide context with savedView mode when viewKey is present', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={['/logs?viewKey="test-view-id"']}>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path="/logs"
|
||||||
|
component={(): JSX.Element => (
|
||||||
|
<PreferenceContextProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</PreferenceContextProvider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('mode')).toHaveTextContent('savedView');
|
||||||
|
expect(screen.getByTestId('dataSource')).toHaveTextContent('logs');
|
||||||
|
expect(screen.getByTestId('savedViewId')).toHaveTextContent('test-view-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set traces dataSource when pathname includes traces', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={['/traces']}>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path="/traces"
|
||||||
|
component={(): JSX.Element => (
|
||||||
|
<PreferenceContextProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</PreferenceContextProvider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('dataSource')).toHaveTextContent('traces');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid viewKey JSON gracefully', () => {
|
||||||
|
// Mock console.error to avoid test output clutter
|
||||||
|
const originalConsoleError = console.error;
|
||||||
|
console.error = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={['/logs?viewKey=invalid-json']}>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path="/logs"
|
||||||
|
component={(): JSX.Element => (
|
||||||
|
<PreferenceContextProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</PreferenceContextProvider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('mode')).toHaveTextContent('direct');
|
||||||
|
expect(console.error).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Restore console.error
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when usePreferenceContext is used outside provider', () => {
|
||||||
|
// Suppress the error output for this test
|
||||||
|
const originalConsoleError = console.error;
|
||||||
|
console.error = jest.fn();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
render(<TestConsumer />);
|
||||||
|
}).toThrow(
|
||||||
|
'usePreferenceContext must be used within PreferenceContextProvider',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore console.error
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,162 @@
|
|||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { defaultLogsSelectedColumns } from 'container/OptionsMenu/constants';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
import { FormattingOptions } from 'providers/preferences/types';
|
||||||
|
import {
|
||||||
|
BaseAutocompleteData,
|
||||||
|
DataTypes,
|
||||||
|
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
import logsLoaderConfig from '../configs/logsLoaderConfig';
|
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const mockLocalStorage: Record<string, string> = {};
|
||||||
|
|
||||||
|
jest.mock('api/browser/localstorage/get', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn((key: string) => mockLocalStorage[key] || null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('logsLoaderConfig', () => {
|
||||||
|
// Save original location object
|
||||||
|
const originalWindowLocation = window.location;
|
||||||
|
let mockedLocation: Partial<Location>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Setup a mocked location object
|
||||||
|
mockedLocation = {
|
||||||
|
...originalWindowLocation,
|
||||||
|
search: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock the window.location property
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: mockedLocation,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear mocked localStorage
|
||||||
|
Object.keys(mockLocalStorage).forEach((key) => {
|
||||||
|
delete mockLocalStorage[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original location
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalWindowLocation,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have priority order: local, url, default', () => {
|
||||||
|
expect(logsLoaderConfig.priority).toEqual(['local', 'url', 'default']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load from localStorage when available', async () => {
|
||||||
|
const mockColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'test-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set up localStorage mock data with the correct key from LOCALSTORAGE enum
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS] = JSON.stringify({
|
||||||
|
selectColumns: mockColumns,
|
||||||
|
maxLines: 10,
|
||||||
|
format: 'json',
|
||||||
|
fontSize: 'large',
|
||||||
|
version: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await logsLoaderConfig.local();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
columns: mockColumns,
|
||||||
|
formatting: {
|
||||||
|
maxLines: 10,
|
||||||
|
format: 'json' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 2,
|
||||||
|
} as FormattingOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid localStorage data gracefully', async () => {
|
||||||
|
// Set up invalid localStorage mock data
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS] = 'invalid-json';
|
||||||
|
|
||||||
|
const result = await logsLoaderConfig.local();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
columns: [] as BaseAutocompleteData[],
|
||||||
|
formatting: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load from URL when available', async () => {
|
||||||
|
const mockColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'url-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set up URL search params
|
||||||
|
mockedLocation.search = `?options=${encodeURIComponent(
|
||||||
|
JSON.stringify({
|
||||||
|
selectColumns: mockColumns,
|
||||||
|
maxLines: 5,
|
||||||
|
format: 'raw',
|
||||||
|
fontSize: 'medium',
|
||||||
|
version: 1,
|
||||||
|
}),
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
const result = await logsLoaderConfig.url();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
columns: mockColumns,
|
||||||
|
formatting: {
|
||||||
|
maxLines: 5,
|
||||||
|
format: 'raw' as LogViewMode,
|
||||||
|
fontSize: 'medium' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
} as FormattingOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid URL data gracefully', async () => {
|
||||||
|
// Set up invalid URL search params
|
||||||
|
mockedLocation.search = '?options=invalid-json';
|
||||||
|
|
||||||
|
const result = await logsLoaderConfig.url();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
columns: [] as BaseAutocompleteData[],
|
||||||
|
formatting: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide default values when no other source is available', async () => {
|
||||||
|
const result = await logsLoaderConfig.default();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
columns: defaultLogsSelectedColumns as BaseAutocompleteData[],
|
||||||
|
formatting: {
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table' as LogViewMode,
|
||||||
|
fontSize: 'small' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
} as FormattingOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,251 @@
|
|||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { defaultOptionsQuery } from 'container/OptionsMenu/constants';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
import { FormattingOptions, PreferenceMode } from 'providers/preferences/types';
|
||||||
|
import {
|
||||||
|
BaseAutocompleteData,
|
||||||
|
DataTypes,
|
||||||
|
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
import getLogsUpdaterConfig from '../configs/logsUpdaterConfig';
|
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const mockLocalStorage: Record<string, string> = {};
|
||||||
|
|
||||||
|
jest.mock('api/browser/localstorage/set', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn((key: string, value: string) => {
|
||||||
|
mockLocalStorage[key] = value;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock localStorage.getItem
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: {
|
||||||
|
getItem: jest.fn((key: string) => mockLocalStorage[key] || null),
|
||||||
|
setItem: jest.fn((key: string, value: string) => {
|
||||||
|
mockLocalStorage[key] = value;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logsUpdaterConfig', () => {
|
||||||
|
// Mock redirectWithOptionsData and setSavedViewPreferences
|
||||||
|
const redirectWithOptionsData = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
// Clear mocked localStorage
|
||||||
|
Object.keys(mockLocalStorage).forEach((key) => {
|
||||||
|
delete mockLocalStorage[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update columns in localStorage for direct mode', () => {
|
||||||
|
const logsUpdater = getLogsUpdaterConfig(
|
||||||
|
redirectWithOptionsData,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'new-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set initial localStorage data
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS] = JSON.stringify({
|
||||||
|
selectColumns: [
|
||||||
|
{
|
||||||
|
key: 'old-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxLines: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
logsUpdater.updateColumns(newColumns, 'direct' as PreferenceMode);
|
||||||
|
|
||||||
|
// Should update URL
|
||||||
|
expect(redirectWithOptionsData).toHaveBeenCalledWith({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
selectColumns: newColumns,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should update localStorage
|
||||||
|
const storedData = JSON.parse(
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS],
|
||||||
|
);
|
||||||
|
expect(storedData.selectColumns).toEqual(newColumns);
|
||||||
|
expect(storedData.maxLines).toBe(2); // Should preserve other fields
|
||||||
|
|
||||||
|
// Should not update saved view preferences
|
||||||
|
expect(setSavedViewPreferences).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update columns in savedViewPreferences for savedView mode', () => {
|
||||||
|
const logsUpdater = getLogsUpdaterConfig(
|
||||||
|
redirectWithOptionsData,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'new-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
logsUpdater.updateColumns(newColumns, 'savedView' as PreferenceMode);
|
||||||
|
|
||||||
|
// Should not update URL in savedView mode
|
||||||
|
expect(redirectWithOptionsData).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Should not update localStorage in savedView mode
|
||||||
|
expect(mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS]).toBeUndefined();
|
||||||
|
|
||||||
|
// Should update saved view preferences
|
||||||
|
expect(setSavedViewPreferences).toHaveBeenCalledWith({
|
||||||
|
columns: newColumns,
|
||||||
|
formatting: {
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table',
|
||||||
|
fontSize: 'small',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update formatting options in localStorage for direct mode', () => {
|
||||||
|
const logsUpdater = getLogsUpdaterConfig(
|
||||||
|
redirectWithOptionsData,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newFormatting: FormattingOptions = {
|
||||||
|
maxLines: 5,
|
||||||
|
format: 'json' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial localStorage data
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS] = JSON.stringify({
|
||||||
|
selectColumns: [
|
||||||
|
{
|
||||||
|
key: 'column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table',
|
||||||
|
});
|
||||||
|
|
||||||
|
logsUpdater.updateFormatting(newFormatting, 'direct' as PreferenceMode);
|
||||||
|
|
||||||
|
// Should always update URL for both modes
|
||||||
|
expect(redirectWithOptionsData).toHaveBeenCalledWith({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
...newFormatting,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should update localStorage in direct mode
|
||||||
|
const storedData = JSON.parse(
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS],
|
||||||
|
);
|
||||||
|
expect(storedData.maxLines).toBe(5);
|
||||||
|
expect(storedData.format).toBe('json');
|
||||||
|
expect(storedData.fontSize).toBe('large');
|
||||||
|
expect(storedData.version).toBe(1);
|
||||||
|
expect(storedData.selectColumns).toEqual([
|
||||||
|
{
|
||||||
|
key: 'column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
]); // Should preserve columns
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update localStorage for savedView mode in updateFormatting', () => {
|
||||||
|
const logsUpdater = getLogsUpdaterConfig(
|
||||||
|
redirectWithOptionsData,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newFormatting: FormattingOptions = {
|
||||||
|
maxLines: 5,
|
||||||
|
format: 'json' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial localStorage data
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS] = JSON.stringify({
|
||||||
|
selectColumns: [
|
||||||
|
{
|
||||||
|
key: 'column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table',
|
||||||
|
});
|
||||||
|
|
||||||
|
logsUpdater.updateFormatting(newFormatting, 'savedView' as PreferenceMode);
|
||||||
|
|
||||||
|
// Should always update URL for both modes
|
||||||
|
expect(redirectWithOptionsData).toHaveBeenCalledWith({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
...newFormatting,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should not override localStorage in savedView mode
|
||||||
|
const storedData = JSON.parse(
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS],
|
||||||
|
);
|
||||||
|
expect(storedData.maxLines).toBe(2); // Should remain the same
|
||||||
|
expect(storedData.format).toBe('table'); // Should remain the same
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize localStorage if it does not exist', () => {
|
||||||
|
const logsUpdater = getLogsUpdaterConfig(
|
||||||
|
redirectWithOptionsData,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newFormatting: FormattingOptions = {
|
||||||
|
maxLines: 5,
|
||||||
|
format: 'json' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// No initial localStorage data
|
||||||
|
|
||||||
|
logsUpdater.updateFormatting(newFormatting, 'direct' as PreferenceMode);
|
||||||
|
|
||||||
|
// Should create localStorage entry
|
||||||
|
const storedData = JSON.parse(
|
||||||
|
mockLocalStorage[LOCALSTORAGE.LOGS_LIST_OPTIONS],
|
||||||
|
);
|
||||||
|
expect(storedData.maxLines).toBe(5);
|
||||||
|
expect(storedData.format).toBe('json');
|
||||||
|
expect(storedData.fontSize).toBe('large');
|
||||||
|
expect(storedData.version).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,139 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import logsLoaderConfig from '../configs/logsLoaderConfig';
|
||||||
|
import { usePreferenceLoader } from '../loader/usePreferenceLoader';
|
||||||
|
|
||||||
|
// Mock the config loaders
|
||||||
|
jest.mock('../configs/logsLoaderConfig', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
priority: ['local', 'url', 'default'],
|
||||||
|
local: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'local-column' }],
|
||||||
|
formatting: { maxLines: 5, format: 'table', fontSize: 'medium', version: 1 },
|
||||||
|
}),
|
||||||
|
url: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'url-column' }],
|
||||||
|
formatting: { maxLines: 3, format: 'table', fontSize: 'small', version: 1 },
|
||||||
|
}),
|
||||||
|
default: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'default-column' }],
|
||||||
|
formatting: { maxLines: 2, format: 'table', fontSize: 'small', version: 1 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../configs/tracesLoaderConfig', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
priority: ['local', 'url', 'default'],
|
||||||
|
local: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'local-trace-column' }],
|
||||||
|
}),
|
||||||
|
url: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'url-trace-column' }],
|
||||||
|
}),
|
||||||
|
default: jest.fn().mockResolvedValue({
|
||||||
|
columns: [{ name: 'default-trace-column' }],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('usePreferenceLoader', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load logs preferences based on priority order', async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceLoader({ dataSource: DataSource.LOGS, reSync: 0 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initially it should be loading
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
expect(result.current.preferences).toBe(null);
|
||||||
|
expect(result.current.error).toBe(null);
|
||||||
|
|
||||||
|
// Wait for the loader to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have loaded from local storage (highest priority)
|
||||||
|
expect(result.current.preferences).toEqual({
|
||||||
|
columns: [{ name: 'local-column' }],
|
||||||
|
formatting: { maxLines: 5, format: 'table', fontSize: 'medium', version: 1 },
|
||||||
|
});
|
||||||
|
expect(result.current.error).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load traces preferences', async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceLoader({ dataSource: DataSource.TRACES, reSync: 0 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the loader to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have loaded trace columns
|
||||||
|
expect(result.current.preferences).toEqual({
|
||||||
|
columns: [{ name: 'local-trace-column' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should re-load preferences when reSync changes', async () => {
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
({ dataSource, reSync }) => usePreferenceLoader({ dataSource, reSync }),
|
||||||
|
{ initialProps: { dataSource: DataSource.LOGS, reSync: 0 } },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the first load to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger a reSync
|
||||||
|
rerender({ dataSource: DataSource.LOGS, reSync: 1 });
|
||||||
|
|
||||||
|
// Should start loading again
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
|
||||||
|
// Wait for the second load to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have reloaded from local storage
|
||||||
|
expect(result.current.preferences).toEqual({
|
||||||
|
columns: [{ name: 'local-column' }],
|
||||||
|
formatting: { maxLines: 5, format: 'table', fontSize: 'medium', version: 1 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors during loading', async () => {
|
||||||
|
// Mock an error in the loader using jest.spyOn
|
||||||
|
const localSpy = jest.spyOn(logsLoaderConfig, 'local');
|
||||||
|
localSpy.mockRejectedValueOnce(new Error('Loading failed'));
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceLoader({ dataSource: DataSource.LOGS, reSync: 0 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the loader to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have set the error
|
||||||
|
expect(result.current.error).toBeInstanceOf(Error);
|
||||||
|
expect(result.current.error?.message).toBe('Loading failed');
|
||||||
|
expect(result.current.preferences).toBe(null);
|
||||||
|
|
||||||
|
// Restore original implementation
|
||||||
|
localSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,212 @@
|
|||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
import { FormattingOptions, PreferenceMode } from 'providers/preferences/types';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import {
|
||||||
|
BaseAutocompleteData,
|
||||||
|
DataTypes,
|
||||||
|
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { usePreferenceUpdater } from '../updater/usePreferenceUpdater';
|
||||||
|
|
||||||
|
// Mock the config updaters
|
||||||
|
const mockUpdateColumns = jest.fn();
|
||||||
|
const mockUpdateFormatting = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('../configs/logsUpdaterConfig', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
updateColumns: mockUpdateColumns,
|
||||||
|
updateFormatting: mockUpdateFormatting,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../configs/tracesUpdaterConfig', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
updateColumns: mockUpdateColumns,
|
||||||
|
updateFormatting: mockUpdateFormatting,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the URL query hook
|
||||||
|
jest.mock('hooks/useUrlQueryData', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockReturnValue({
|
||||||
|
redirectWithQuery: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('usePreferenceUpdater', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return updateColumns and updateFormatting functions', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should return the update functions
|
||||||
|
expect(typeof result.current.updateColumns).toBe('function');
|
||||||
|
expect(typeof result.current.updateFormatting).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the logs updater for updateColumns with logs dataSource', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
const newColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'new-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateColumns(newColumns);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should call the logs updater
|
||||||
|
expect(mockUpdateColumns).toHaveBeenCalledWith(newColumns, 'direct');
|
||||||
|
expect(setReSync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the logs updater for updateFormatting with logs dataSource', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
const newFormatting: FormattingOptions = {
|
||||||
|
maxLines: 10,
|
||||||
|
format: 'table' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateFormatting(newFormatting);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should call the logs updater
|
||||||
|
expect(mockUpdateFormatting).toHaveBeenCalledWith(newFormatting, 'direct');
|
||||||
|
expect(setReSync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the traces updater for updateColumns with traces dataSource', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
const newColumns: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
key: 'new-trace-column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateColumns(newColumns);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should call the traces updater
|
||||||
|
expect(mockUpdateColumns).toHaveBeenCalledWith(newColumns, 'direct');
|
||||||
|
expect(setReSync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the traces updater for updateFormatting with traces dataSource', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
const newFormatting: FormattingOptions = {
|
||||||
|
maxLines: 10,
|
||||||
|
format: 'table' as LogViewMode,
|
||||||
|
fontSize: 'large' as FontSize,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateFormatting(newFormatting);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should call the traces updater
|
||||||
|
expect(mockUpdateFormatting).toHaveBeenCalledWith(newFormatting, 'direct');
|
||||||
|
expect(setReSync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should increment reSync counter when updates are called', () => {
|
||||||
|
const setReSync = jest.fn();
|
||||||
|
const setSavedViewPreferences = jest.fn();
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePreferenceUpdater({
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
mode: 'direct' as PreferenceMode,
|
||||||
|
setReSync,
|
||||||
|
setSavedViewPreferences,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateColumns([
|
||||||
|
{
|
||||||
|
key: 'column',
|
||||||
|
type: 'tag',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(setReSync).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
|
||||||
|
// Simulate the setReSync callback to ensure it increments
|
||||||
|
const incrementFn = setReSync.mock.calls[0][0];
|
||||||
|
expect(incrementFn(1)).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
@ -37,10 +37,6 @@ export function usePreferenceSync({
|
|||||||
)?.extraData;
|
)?.extraData;
|
||||||
|
|
||||||
const parsedExtraData = JSON.parse(extraData || '{}');
|
const parsedExtraData = JSON.parse(extraData || '{}');
|
||||||
console.log('uncaught extraData', {
|
|
||||||
extraData,
|
|
||||||
parsedExtraData,
|
|
||||||
});
|
|
||||||
let columns: BaseAutocompleteData[] = [];
|
let columns: BaseAutocompleteData[] = [];
|
||||||
let formatting: FormattingOptions | undefined;
|
let formatting: FormattingOptions | undefined;
|
||||||
if (dataSource === DataSource.LOGS) {
|
if (dataSource === DataSource.LOGS) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user