Загрузка данных
import { DataSourceProvider } from '../dataSourceProvide';
import { requestBars, requestRealtimeBars } from '../../requestBars';
import { moexChartTimeConverter } from '@utils/chartToReqTimeConverter';
import { Timeframes } from 'moex-chart';
// Mock the dependencies
jest.mock('../../requestBars');
jest.mock('@utils/chartToReqTimeConverter');
describe('DataSourceProvider', () => {
const mockRequestBars = requestBars as jest.Mock;
const mockRequestRealtimeBars = requestRealtimeBars as jest.Mock;
const mockMoexChartTimeConverter = moexChartTimeConverter as jest.Mock;
const mockBar = {
time: 1640995200,
open: 100,
close: 110,
high: 120,
low: 90,
volume: 1000,
};
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
jest.setSystemTime(new Date('2026-05-19T10:00:00Z'));
});
afterEach(() => {
jest.useRealTimers();
});
it('should request chart history data with converted timeframe', async () => {
// Arrange
const mockTimeframeCallback = jest.fn();
mockMoexChartTimeConverter.mockReturnValue('1');
mockRequestBars.mockResolvedValue([mockBar]);
const dataSource = DataSourceProvider.getDataSource(undefined, mockTimeframeCallback);
// Act
const result = await dataSource(Timeframes['1m'], 'MOEX:SBER');
// Assert
expect(mockTimeframeCallback).toHaveBeenCalledWith(Timeframes['1m']);
expect(mockMoexChartTimeConverter).toHaveBeenCalledWith(Timeframes['1m']);
expect(mockRequestBars).toHaveBeenCalledWith({
currencyPair: 'MOEX.SBER',
interval: '1',
periodParams: {
firstDataRequest: true,
to: Math.round(Date.now() / 1000),
from: Date.now(),
countBack: 2000,
},
ticker: 'MOEX:SBER',
indicativeData: undefined,
});
expect(result).toEqual([mockBar]);
});
it('should use until time when it is provided', async () => {
// Arrange
mockMoexChartTimeConverter.mockReturnValue('5');
mockRequestBars.mockResolvedValue([mockBar]);
const dataSource = DataSourceProvider.getDataSource();
// Act
await dataSource(Timeframes['5m'], 'MOEX:GAZP', { time: 111 } as any);
// Assert
expect(mockRequestBars).toHaveBeenCalledWith(
expect.objectContaining({
periodParams: expect.objectContaining({
to: 111,
}),
}),
);
});
it('should return null when history data is empty', async () => {
// Arrange
mockMoexChartTimeConverter.mockReturnValue('1');
mockRequestBars.mockResolvedValue([]);
const dataSource = DataSourceProvider.getDataSource();
// Act
const result = await dataSource(Timeframes['1m'], 'MOEX:SBER');
// Assert
expect(result).toBeNull();
});
it('should request realtime data and update normalized symbol', async () => {
// Arrange
const provider = new DataSourceProvider();
const mockUpdate = jest.fn();
mockMoexChartTimeConverter.mockReturnValue('1');
mockRequestRealtimeBars.mockResolvedValue(mockBar);
provider.startRealtime({
getSymbols: () => [' moex:sber '],
getTimeframe: () => Timeframes['1m'],
update: mockUpdate,
periodMs: 1000,
});
// Act
await jest.advanceTimersByTimeAsync(1000);
// Assert
expect(mockRequestRealtimeBars).toHaveBeenCalledWith({
currencyPair: 'MOEX.SBER',
interval: '1',
ticker: 'MOEX:SBER',
indicativeData: undefined,
});
expect(mockUpdate).toHaveBeenCalledWith('MOEX:SBER', mockBar);
});
it('should not call update when realtime data is empty', async () => {
// Arrange
const provider = new DataSourceProvider();
const mockUpdate = jest.fn();
mockMoexChartTimeConverter.mockReturnValue('1');
mockRequestRealtimeBars.mockResolvedValue(undefined);
provider.startRealtime({
getSymbols: () => ['MOEX:SBER'],
getTimeframe: () => Timeframes['1m'],
update: mockUpdate,
periodMs: 1000,
});
// Act
await jest.advanceTimersByTimeAsync(1000);
// Assert
expect(mockUpdate).not.toHaveBeenCalled();
});
it('should clear realtime timer on unsubscribe', async () => {
// Arrange
const provider = new DataSourceProvider();
mockMoexChartTimeConverter.mockReturnValue('1');
mockRequestRealtimeBars.mockResolvedValue(mockBar);
const unsubscribe = provider.startRealtime({
getSymbols: () => ['MOEX:SBER'],
getTimeframe: () => Timeframes['1m'],
update: jest.fn(),
periodMs: 1000,
});
// Act
unsubscribe();
await jest.advanceTimersByTimeAsync(1000);
// Assert
expect(mockRequestRealtimeBars).not.toHaveBeenCalled();
});
});
import { act, render } from '@testing-library/react';
import { useChangeProperties, useSelectProperties } from '@modules/widgetProperties';
import { useMoexChart } from '../hooks/useMoexChart';
import { DataSourceProvider, dataSourceProvider } from '../dataSourceProvide';
import { MoexChart, Timeframes } from 'moex-chart';
// Mock the dependencies
jest.mock('@modules/widgetProperties');
jest.mock('../dataSourceProvide', () => ({
DataSourceProvider: {
getDataSource: jest.fn(),
},
dataSourceProvider: {
startRealtime: jest.fn(),
},
}));
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',
},
}));
describe('useMoexChart', () => {
const mockUseSelectProperties = useSelectProperties as jest.Mock;
const mockUseChangeProperties = useChangeProperties as jest.Mock;
const mockMoexChart = MoexChart as jest.Mock;
const mockGetDataSource = DataSourceProvider.getDataSource as jest.Mock;
const mockStartRealtime = dataSourceProvider.startRealtime as jest.Mock;
const mockUpdateProperties = jest.fn();
const mockDestroy = jest.fn();
const mockGetSnapshot = jest.fn();
const mockSetSnapshot = jest.fn();
const mockGetCompareManager = jest.fn();
const mockCompareManager = {
id: 'compare-manager',
};
const mockSnapshot = {
charts: [
{
symbol: 'OLD:SYMBOL',
timeframe: Timeframes['5m'],
chartSeriesType: 'Candlestick',
panes: [],
},
],
};
let hookResult: ReturnType<typeof useMoexChart> | null = null;
let lastMoexChartConfig: any = null;
let mockMoexChartState: any = null;
const TestComponent = ({ symbol = 'MOEX:SBER' }: { symbol?: string }) => {
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) => selector({ moexChartState: mockMoexChartState }));
mockUseChangeProperties.mockReturnValue({
updateProperties: mockUpdateProperties,
});
mockGetDataSource.mockReturnValue(jest.fn());
mockStartRealtime.mockReturnValue(jest.fn());
mockGetSnapshot.mockReturnValue(mockSnapshot);
mockGetCompareManager.mockReturnValue(mockCompareManager);
mockMoexChart.mockImplementation((config) => {
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(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) => 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];
const mockState = {
moexChartState: {},
};
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) => 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 update timeframe from data source callback', () => {
// Arrange
render(<TestComponent symbol="MOEX:SBER" />);
const getDataSourceCallback = mockGetDataSource.mock.calls[0][1];
// Act
act(() => {
getDataSourceCallback(Timeframes['5m']);
});
// Assert
expect(mockUpdateProperties).toHaveBeenCalled();
const updateCallback = mockUpdateProperties.mock.calls[0][0];
const mockState = {
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 getDataSourceCallback = mockGetDataSource.mock.calls[0][1];
// Act
act(() => {
getDataSourceCallback(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();
});
});