Загрузка данных
import React from 'react';
import { act, render } from '@testing-library/react';
import { MoexChart, Timeframes } from 'moex-chart';
import { useChangeProperties, useSelectProperties } from '@modules/widgetProperties';
import { useMoexChart } from '../components/MoexChart/hooks';
const mockGetDataSource = jest.fn();
const mockStartRealtime = jest.fn();
interface MockDataSourceProviderInstance {
startRealtime: jest.Mock;
}
interface MockDataSourceProviderConstructor extends jest.Mock<MockDataSourceProviderInstance, []> {
getDataSource: jest.Mock;
}
const mockDataSourceProviderConstructor = jest.fn(() => ({
startRealtime: mockStartRealtime,
})) as MockDataSourceProviderConstructor;
mockDataSourceProviderConstructor.getDataSource = mockGetDataSource;
// Mock the dependencies
jest.mock('@modules/widgetProperties');
jest.mock('../components/MoexChart/dataSourceProvide', () => ({
DataSourceProvider: mockDataSourceProviderConstructor,
}));
jest.mock('@widgets/Chart/components/MoexChart/dataSourceProvide', () => ({
DataSourceProvider: mockDataSourceProviderConstructor,
}));
jest.mock('moex-chart', () => ({
MoexChart: jest.fn(),
Timeframes: {
'10s': '10s',
'1m': '1m',
'5m': '5m',
},
DateFormat: {
DD_MM_YYYY_HH_mm_ss: 'DD_MM_YYYY_HH_mm_ss',
},
IndicatorsIds: {
Volume: 'Volume',
},
}));
type TimeframeValue = (typeof Timeframes)[keyof typeof Timeframes];
interface MockPaneSnapshot {
indicators: unknown[];
}
interface MockChartSnapshot {
charts: {
symbol: string;
timeframe: TimeframeValue;
chartSeriesType: string;
panes: MockPaneSnapshot[];
}[];
}
interface MockMoexChartConfig {
container: HTMLElement;
snapshot: MockChartSnapshot;
chartCollectionPreset: {
openCompareModal: () => void;
getDataSource: jest.Mock;
startRealtime: (
getSymbols: () => string[],
getTimeframe: () => TimeframeValue,
update: jest.Mock,
) => (() => void) | undefined;
};
}
interface MockMoexChartState {
tf?: TimeframeValue;
savedData?: string;
}
interface MockPropertiesState {
moexChartState?: MockMoexChartState;
}
type UpdatePropertiesCallback = (state: MockPropertiesState) => void;
type TimeframeChangeCallback = (timeframe: TimeframeValue) => void;
interface TestComponentProps {
symbol?: string;
}
describe('useMoexChart', () => {
const mockUseSelectProperties = useSelectProperties as jest.Mock;
const mockUseChangeProperties = useChangeProperties as jest.Mock;
const mockMoexChart = MoexChart as jest.Mock;
const mockUpdateProperties = jest.fn();
const mockDestroy = jest.fn();
const mockGetSnapshot = jest.fn();
const mockSetSnapshot = jest.fn();
const mockGetCompareManager = jest.fn();
const mockDataSource = jest.fn();
const mockRealtimeUnsubscribe = jest.fn();
const mockCompareManager = {
id: 'compare-manager',
};
const mockSnapshot: MockChartSnapshot = {
charts: [
{
symbol: 'OLD:SYMBOL',
timeframe: Timeframes['5m'],
chartSeriesType: 'Candlestick',
panes: [
{
indicators: [],
},
],
},
],
};
let hookResult: ReturnType<typeof useMoexChart> | null = null;
let lastMoexChartConfig: MockMoexChartConfig | null = null;
let mockMoexChartState: MockMoexChartState | undefined;
const TestComponent = ({ symbol = 'MOEX:SBER' }: TestComponentProps): React.ReactElement => {
hookResult = useMoexChart({
symbol,
indicativeData: undefined,
});
return <div ref={hookResult.containerRef} />;
};
beforeEach(() => {
jest.clearAllMocks();
hookResult = null;
lastMoexChartConfig = null;
mockMoexChartState = {
tf: Timeframes['1m'],
savedData: undefined,
};
mockUseSelectProperties.mockImplementation((selector: (state: MockPropertiesState) => unknown) =>
selector({
moexChartState: mockMoexChartState,
}),
);
mockUseChangeProperties.mockReturnValue({
updateProperties: mockUpdateProperties,
});
mockGetDataSource.mockReturnValue(mockDataSource);
mockStartRealtime.mockReturnValue(mockRealtimeUnsubscribe);
mockGetSnapshot.mockReturnValue(mockSnapshot);
mockGetCompareManager.mockReturnValue(mockCompareManager);
mockMoexChart.mockImplementation((config: MockMoexChartConfig) => {
lastMoexChartConfig = config;
return {
destroy: mockDestroy,
getSnapshot: mockGetSnapshot,
setSnapshot: mockSetSnapshot,
getCompareManager: mockGetCompareManager,
};
});
});
it('should create moex chart with current symbol and timeframe', () => {
// Arrange & Act
render(<TestComponent symbol="MOEX:SBER" />);
// Assert
expect(mockMoexChart).toHaveBeenCalledTimes(1);
expect(mockDataSourceProviderConstructor).toHaveBeenCalledTimes(1);
expect(lastMoexChartConfig?.container).toBeInstanceOf(HTMLDivElement);
expect(lastMoexChartConfig?.snapshot.charts[0]?.symbol).toBe('MOEX:SBER');
expect(lastMoexChartConfig?.snapshot.charts[0]?.timeframe).toBe(Timeframes['1m']);
expect(hookResult?.compareManagerRef.current).toBe(mockCompareManager);
});
it('should create chart with saved snapshot when saved data exists', () => {
// Arrange
mockMoexChartState = {
tf: Timeframes['5m'],
savedData: JSON.stringify(mockSnapshot),
};
mockUseSelectProperties.mockImplementation((selector: (state: MockPropertiesState) => unknown) =>
selector({
moexChartState: mockMoexChartState,
}),
);
// Act
render(<TestComponent symbol="MOEX:GAZP" />);
// Assert
expect(lastMoexChartConfig?.snapshot.charts[0]?.symbol).toBe('MOEX:GAZP');
expect(lastMoexChartConfig?.snapshot.charts[0]?.timeframe).toBe(Timeframes['5m']);
expect(hookResult?.hasSavedSnapshot).toBe(true);
});
it('should save chart snapshot to widget properties', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
// Act
act(() => {
hookResult?.saveSnapshot();
});
// Assert
expect(mockGetSnapshot).toHaveBeenCalled();
expect(mockUpdateProperties).toHaveBeenCalled();
const updateCallback = mockUpdateProperties.mock.calls[0]?.[0] as UpdatePropertiesCallback;
const mockState: MockPropertiesState = {
moexChartState: {
tf: Timeframes['1m'],
},
};
updateCallback(mockState);
expect(mockState.moexChartState).toEqual({
tf: Timeframes['1m'],
savedData: JSON.stringify(mockSnapshot),
});
});
it('should not save snapshot when chart does not return snapshot', () => {
// Arrange
mockGetSnapshot.mockReturnValue(undefined);
render(<TestComponent symbol="MOEX:SBER" />);
// Act
act(() => {
hookResult?.saveSnapshot();
});
// Assert
expect(mockUpdateProperties).not.toHaveBeenCalled();
});
it('should apply saved snapshot with current symbol and timeframe', () => {
// Arrange
mockMoexChartState = {
tf: Timeframes['5m'],
savedData: JSON.stringify(mockSnapshot),
};
mockUseSelectProperties.mockImplementation((selector: (state: MockPropertiesState) => unknown) =>
selector({
moexChartState: mockMoexChartState,
}),
);
render(<TestComponent symbol="MOEX:SBER" />);
// Act
act(() => {
hookResult?.applySnapshot();
});
// Assert
expect(mockSetSnapshot).toHaveBeenCalledWith({
...mockSnapshot,
charts: [
{
...mockSnapshot.charts[0],
symbol: 'MOEX:SBER',
timeframe: Timeframes['5m'],
},
],
});
expect(hookResult?.compareManagerRef.current).toBe(mockCompareManager);
});
it('should update compare modal state', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
// Act
act(() => {
hookResult?.setIsCompareOpen(true);
});
// Assert
expect(hookResult?.isCompareOpen).toBe(true);
});
it('should open compare modal from chart preset callback', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
// Act
act(() => {
lastMoexChartConfig?.chartCollectionPreset.openCompareModal();
});
// Assert
expect(hookResult?.isCompareOpen).toBe(true);
});
it('should pass realtime params to data source provider', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
const getSymbols = jest.fn(() => ['MOEX:SBER']);
const getTimeframe = jest.fn(() => Timeframes['1m']);
const update = jest.fn();
// Act
const unsubscribe = lastMoexChartConfig?.chartCollectionPreset.startRealtime(getSymbols, getTimeframe, update);
// Assert
expect(mockStartRealtime).toHaveBeenCalledWith({
getSymbols,
getTimeframe,
update,
});
expect(unsubscribe).toBe(mockRealtimeUnsubscribe);
});
it('should update timeframe from data source callback', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
const timeframeChangeCallback = mockGetDataSource.mock.calls[0]?.[1] as TimeframeChangeCallback;
// Act
act(() => {
timeframeChangeCallback(Timeframes['5m']);
});
// Assert
expect(mockUpdateProperties).toHaveBeenCalled();
const updateCallback = mockUpdateProperties.mock.calls[0]?.[0] as UpdatePropertiesCallback;
const mockState: MockPropertiesState = {
moexChartState: {
tf: Timeframes['1m'],
},
};
updateCallback(mockState);
expect(mockState.moexChartState?.tf).toBe(Timeframes['5m']);
});
it('should not update timeframe when it is the same as current timeframe', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
const timeframeChangeCallback = mockGetDataSource.mock.calls[0]?.[1] as TimeframeChangeCallback;
// Act
act(() => {
timeframeChangeCallback(Timeframes['1m']);
});
// Assert
expect(mockUpdateProperties).not.toHaveBeenCalled();
});
it('should destroy chart on unmount', () => {
// Arrange
const { unmount } = render(<TestComponent symbol="MOEX:SBER" />);
// Act
unmount();
// Assert
expect(mockDestroy).toHaveBeenCalled();
});
});