Загрузка данных
import { BehaviorSubject, Subscription } from 'rxjs';
import { Chart } from '@core/Chart';
import { Intervals } from '@src/types/intervals';
interface ChartInternals {
eventManager: {
symbolId: () => BehaviorSubject<string>;
getInterval: () => BehaviorSubject<Intervals | null>;
getTimeframe: jest.Mock;
};
compareManager: {
itemsObs: () => BehaviorSubject<{ symbolId: string }[]>;
};
dataSource: {
setSymbols: jest.Mock;
loadTill: jest.Mock;
loadAllHistory: jest.Mock;
loadMoreHistory: jest.Mock;
getOldestTime: jest.Mock;
getLastCandle: jest.Mock;
};
lwcChart: {
timeScale: () => {
getVisibleRange: jest.Mock;
setVisibleRange: jest.Mock;
getVisibleLogicalRange: jest.Mock;
};
};
mouseEvents: {
subscribe: jest.Mock;
};
paneManager: {
setVisibleLogicalRange: jest.Mock;
};
subscriptions: Subscription;
activeSymbolIds: string[];
active: boolean;
historyBatchRunning: boolean;
setupDataSourceSubs: () => void;
setupHistoricalDataLoading: () => void;
scheduleHistoryBatch: () => void;
}
interface CreateChartOptions {
symbolId?: string;
interval?: Intervals | null;
compareSymbolIds?: string[];
}
interface ChartTestContext {
chart: ChartInternals;
symbolId$: BehaviorSubject<string>;
interval$: BehaviorSubject<Intervals | null>;
getTimeframe: jest.Mock;
compareItems$: BehaviorSubject<{ symbolId: string }[]>;
setSymbols: jest.Mock;
loadTill: jest.Mock;
loadAllHistory: jest.Mock;
loadMoreHistory: jest.Mock;
getOldestTime: jest.Mock;
getLastCandle: jest.Mock;
getVisibleRange: jest.Mock;
getVisibleLogicalRange: jest.Mock;
setVisibleRange: jest.Mock;
mouseEventsSubscribe: jest.Mock;
setVisibleLogicalRange: jest.Mock;
}
interface Deferred {
promise: Promise<void>;
resolve: () => void;
}
function createDeferred(): Deferred {
let resolve!: () => void;
const promise = new Promise<void>((resolver) => {
resolve = resolver;
});
return { promise, resolve };
}
async function flushPromises(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
}
describe('Chart', () => {
let animationFrameCallbacks: FrameRequestCallback[];
let subscriptions: Subscription[];
beforeEach(() => {
animationFrameCallbacks = [];
subscriptions = [];
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
animationFrameCallbacks.push(callback);
return animationFrameCallbacks.length;
});
});
afterEach(() => {
subscriptions.forEach((subscription) => subscription.unsubscribe());
jest.restoreAllMocks();
});
function runNextAnimationFrame(): void {
const callback = animationFrameCallbacks.shift();
callback?.(0);
}
function createChartInternals({
symbolId = '',
interval = null,
compareSymbolIds = ['SBER', 'SBER'],
}: CreateChartOptions = {}): ChartTestContext {
const symbolId$ = new BehaviorSubject(symbolId);
const interval$ = new BehaviorSubject<Intervals | null>(interval);
const compareItems$ = new BehaviorSubject(
compareSymbolIds.map((value) => ({
symbolId: value,
})),
);
const setSymbols = jest.fn();
const loadTill = jest.fn(() => Promise.resolve());
const loadAllHistory = jest.fn(() => Promise.resolve());
const loadMoreHistory = jest.fn(() => Promise.resolve());
const getOldestTime = jest.fn(() => 100);
const getLastCandle = jest.fn(() => ({ time: 200 }));
const getVisibleRange = jest.fn(() => null);
const getVisibleLogicalRange = jest.fn(() => ({ from: 0, to: 1000 }));
const setVisibleRange = jest.fn();
const getTimeframe = jest.fn(() => '1m');
const mouseEventsSubscribe = jest.fn();
const setVisibleLogicalRange = jest.fn();
const chart = Object.assign(Object.create(Chart.prototype), {
eventManager: {
symbolId: () => symbolId$,
getInterval: () => interval$,
getTimeframe,
},
compareManager: {
itemsObs: () => compareItems$,
},
dataSource: {
setSymbols,
loadTill,
loadAllHistory,
loadMoreHistory,
getOldestTime,
getLastCandle,
},
lwcChart: {
timeScale: () => ({
getVisibleRange,
getVisibleLogicalRange,
setVisibleRange,
}),
},
mouseEvents: {
subscribe: mouseEventsSubscribe,
},
paneManager: {
setVisibleLogicalRange,
},
subscriptions: new Subscription(),
activeSymbolIds: [],
active: true,
historyBatchRunning: false,
}) as ChartInternals;
subscriptions.push(chart.subscriptions);
return {
chart,
symbolId$,
interval$,
getTimeframe,
compareItems$,
setSymbols,
loadTill,
loadAllHistory,
loadMoreHistory,
getOldestTime,
getLastCandle,
getVisibleRange,
getVisibleLogicalRange,
setVisibleRange,
mouseEventsSubscribe,
setVisibleLogicalRange,
};
}
it('должен исключать пустой основной symbolId и удалять дубликаты', () => {
const { chart, setSymbols } = createChartInternals();
chart.setupDataSourceSubs();
expect(setSymbols).toHaveBeenCalledTimes(1);
expect(setSymbols).toHaveBeenCalledWith(['SBER']);
});
it('должен зарегистрировать символ до загрузки данных начального интервала', () => {
const callOrder: string[] = [];
const { chart, setSymbols, loadTill } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
setSymbols.mockImplementation(() => {
callOrder.push('setSymbols');
});
loadTill.mockImplementation(() => {
callOrder.push('loadTill');
return Promise.resolve();
});
chart.setupDataSourceSubs();
expect(callOrder).toEqual(['setSymbols', 'loadTill']);
});
it('должен применить начальный интервал после загрузки данных', async () => {
const { chart, loadTill, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
chart.setupDataSourceSubs();
expect(loadTill).toHaveBeenCalledTimes(1);
expect(loadTill).toHaveBeenCalledWith('SBER', expect.any(Number));
await flushPromises();
expect(setVisibleRange).toHaveBeenCalledTimes(1);
expect(setVisibleRange).toHaveBeenCalledWith({
from: expect.any(Number),
to: expect.any(Number),
});
});
it('не должен применять интервал при отсутствии данных', async () => {
const { chart, getOldestTime, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
getOldestTime.mockReturnValue(null);
chart.setupDataSourceSubs();
await flushPromises();
expect(setVisibleRange).not.toHaveBeenCalled();
});
it('не должен применять результат устаревшего интервала', async () => {
const firstLoad = createDeferred();
const secondLoad = createDeferred();
const { chart, interval$, loadTill, loadAllHistory, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
loadTill.mockReturnValue(firstLoad.promise);
loadAllHistory.mockReturnValue(secondLoad.promise);
chart.setupDataSourceSubs();
expect(loadTill).toHaveBeenCalledTimes(1);
interval$.next(Intervals.All);
expect(loadAllHistory).toHaveBeenCalledTimes(1);
secondLoad.resolve();
await flushPromises();
expect(setVisibleRange).toHaveBeenCalledTimes(1);
expect(setVisibleRange).toHaveBeenCalledWith({
from: 100,
to: 200,
});
firstLoad.resolve();
await flushPromises();
expect(setVisibleRange).toHaveBeenCalledTimes(1);
});
it('не должен повторно регистрировать символы при смене только интервала', () => {
const { chart, interval$, setSymbols } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
chart.setupDataSourceSubs();
expect(setSymbols).toHaveBeenCalledTimes(1);
interval$.next(Intervals.All);
expect(setSymbols).toHaveBeenCalledTimes(1);
expect(setSymbols).toHaveBeenCalledWith(['SBER']);
});
it('должен зарегистрировать новый основной символ и применить к нему активный интервал', async () => {
const { chart, symbolId$, setSymbols, loadTill } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
chart.setupDataSourceSubs();
await flushPromises();
setSymbols.mockClear();
loadTill.mockClear();
symbolId$.next('GAZP');
expect(setSymbols).toHaveBeenCalledTimes(1);
expect(setSymbols).toHaveBeenCalledWith(['GAZP']);
expect(loadTill).toHaveBeenCalledTimes(1);
expect(loadTill).toHaveBeenCalledWith('GAZP', expect.any(Number));
});
it('должен загрузить всю историю для интервала All и применить общий диапазон', async () => {
const { chart, loadAllHistory, getOldestTime, getLastCandle, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: ['GAZP'],
interval: Intervals.All,
});
getOldestTime.mockImplementation((symbolId: string) => (symbolId === 'SBER' ? 100 : 50));
getLastCandle.mockImplementation((symbolId: string) => ({
time: symbolId === 'SBER' ? 200 : 300,
}));
chart.setupDataSourceSubs();
expect(loadAllHistory).toHaveBeenCalledTimes(2);
expect(loadAllHistory).toHaveBeenCalledWith('SBER');
expect(loadAllHistory).toHaveBeenCalledWith('GAZP');
await flushPromises();
expect(setVisibleRange).toHaveBeenCalledTimes(1);
expect(setVisibleRange).toHaveBeenCalledWith({
from: 50,
to: 300,
});
});
it('должен прогреть добавленный символ при отсутствии активного интервала', async () => {
const { chart, getVisibleRange, loadTill } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: null,
});
getVisibleRange.mockReturnValue({
from: 100,
to: 200,
});
chart.setupDataSourceSubs();
expect(loadTill).toHaveBeenCalledTimes(1);
expect(loadTill).toHaveBeenCalledWith('SBER', 100);
await flushPromises();
});
it('не должен применять интервал после отписки', async () => {
const loading = createDeferred();
const { chart, loadTill, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
loadTill.mockReturnValue(loading.promise);
chart.setupDataSourceSubs();
expect(loadTill).toHaveBeenCalledTimes(1);
chart.subscriptions.unsubscribe();
loading.resolve();
await flushPromises();
expect(setVisibleRange).not.toHaveBeenCalled();
});
it('не должен повторно догружать историю, если старейшая свеча не изменилась', async () => {
const { chart, loadMoreHistory, getOldestTime, getVisibleLogicalRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
});
chart.activeSymbolIds = ['SBER'];
getOldestTime.mockReturnValue(100);
chart.scheduleHistoryBatch();
runNextAnimationFrame();
await flushPromises();
expect(loadMoreHistory).toHaveBeenCalledTimes(1);
expect(getVisibleLogicalRange).not.toHaveBeenCalled();
expect(animationFrameCallbacks).toHaveLength(0);
});
it('должен продолжить догрузку истории, если старейшая свеча сдвинулась назад', async () => {
const { chart, loadMoreHistory, getOldestTime, getVisibleLogicalRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
});
let oldestTime = 100;
chart.activeSymbolIds = ['SBER'];
getOldestTime.mockImplementation(() => oldestTime);
loadMoreHistory.mockImplementation(() => {
oldestTime = 90;
return Promise.resolve();
});
chart.scheduleHistoryBatch();
runNextAnimationFrame();
await flushPromises();
expect(loadMoreHistory).toHaveBeenCalledTimes(1);
expect(getVisibleLogicalRange).toHaveBeenCalledTimes(1);
expect(animationFrameCallbacks).toHaveLength(1);
});
it('должен считать появление первой старейшей свечи прогрессом', async () => {
const { chart, loadMoreHistory, getOldestTime } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
});
let oldestTime: number | null = null;
chart.activeSymbolIds = ['SBER'];
getOldestTime.mockImplementation(() => oldestTime);
loadMoreHistory.mockImplementation(() => {
oldestTime = 100;
return Promise.resolve();
});
chart.scheduleHistoryBatch();
runNextAnimationFrame();
await flushPromises();
expect(animationFrameCallbacks).toHaveLength(1);
});
it('не должен останавливать новую цепочку загрузки при смене символа во время запроса', async () => {
const loading = createDeferred();
const { chart, loadMoreHistory, getOldestTime } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
});
chart.activeSymbolIds = ['SBER'];
getOldestTime.mockImplementation((symbolId: string) => (symbolId === 'SBER' ? 100 : 200));
loadMoreHistory.mockReturnValue(loading.promise);
chart.scheduleHistoryBatch();
runNextAnimationFrame();
chart.activeSymbolIds = ['GAZP'];
loading.resolve();
await flushPromises();
expect(animationFrameCallbacks).toHaveLength(1);
});
it('не должен останавливать новую цепочку загрузки при смене таймфрейма во время запроса', async () => {
const loading = createDeferred();
const { chart, getTimeframe, loadMoreHistory, getOldestTime } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
});
let timeframe = '1m';
chart.activeSymbolIds = ['SBER'];
getTimeframe.mockImplementation(() => timeframe);
getOldestTime.mockReturnValue(100);
loadMoreHistory.mockReturnValue(loading.promise);
chart.scheduleHistoryBatch();
runNextAnimationFrame();
timeframe = '5m';
loading.resolve();
await flushPromises();
expect(animationFrameCallbacks).toHaveLength(1);
});
it('должен запускать фоновую догрузку для интервала All', async () => {
const { chart, loadMoreHistory, mouseEventsSubscribe, setVisibleLogicalRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals.All,
});
chart.setupDataSourceSubs();
chart.setupHistoricalDataLoading();
const visibleLogicalRangeChangeHandler = mouseEventsSubscribe.mock.calls[0][1] as (
logicalRange: { from: number; to: number } | null,
) => void;
visibleLogicalRangeChangeHandler({ from: 0, to: 1000 });
expect(setVisibleLogicalRange).toHaveBeenCalledWith({
from: 0,
to: 1000,
});
expect(animationFrameCallbacks).toHaveLength(1);
runNextAnimationFrame();
await flushPromises();
expect(loadMoreHistory).toHaveBeenCalledTimes(1);
expect(animationFrameCallbacks).toHaveLength(0);
});
it('не должен применять интервал к неактивному графику', async () => {
const { chart, loadTill, setVisibleRange } = createChartInternals({
symbolId: 'SBER',
compareSymbolIds: [],
interval: Intervals['1Y'],
});
chart.active = false;
chart.setupDataSourceSubs();
await flushPromises();
expect(loadTill).toHaveBeenCalledTimes(1);
expect(setVisibleRange).not.toHaveBeenCalled();
});
it('не должен планировать догрузку для неактивного графика', () => {
const { chart, loadMoreHistory } = createChartInternals();
chart.activeSymbolIds = ['SBER'];
chart.active = false;
chart.scheduleHistoryBatch();
expect(animationFrameCallbacks).toHaveLength(0);
expect(loadMoreHistory).not.toHaveBeenCalled();
});
it('не должен начинать запрос, если график выключен до выполнения кадра', () => {
const { chart, loadMoreHistory } = createChartInternals();
chart.activeSymbolIds = ['SBER'];
chart.scheduleHistoryBatch();
chart.active = false;
runNextAnimationFrame();
expect(loadMoreHistory).not.toHaveBeenCalled();
expect(chart.historyBatchRunning).toBe(false);
});
});
import { EMPTY } from 'rxjs';
import { type IMoexChart, MoexChart } from '@core/MoexChart';
import type { ReactElement } from 'react';
interface HeaderTestProps {
copyImage?: () => Promise<void>;
setChartView?: (view: unknown) => void;
seasonalityStateObs: typeof EMPTY;
}
interface HeaderTestOptions {
showSnapshotButton?: boolean;
showSeasonalityButton?: boolean;
seasonalityActive?: boolean;
}
function createTestContext() {
const drawingsCollectionManager = {
selectedDrawing: jest.fn(),
updateSelectedDrawingSettings: jest.fn(),
toggleSelectedDrawingLock: jest.fn(),
openSelectedDrawingSettings: jest.fn(),
deleteSelectedDrawing: jest.fn(),
};
const mainChart = {
copyImage: jest.fn().mockResolvedValue(undefined),
getDrawingsCollectionManager: () => drawingsCollectionManager,
};
const seasonalityManager = {
back: jest.fn(),
getState: jest.fn(() => EMPTY),
isActive: jest.fn(() => false),
copyImage: jest.fn().mockResolvedValue(undefined),
};
const eventManager = {
clearSymbol: jest.fn(),
setSymbol: jest.fn(),
getTimeframeObs: jest.fn(),
setTimeframe: jest.fn(),
getSelectedSeries: jest.fn(),
setSeriesSelected: jest.fn(),
};
const headerRenderer = { renderComponent: jest.fn() };
const setChartView = jest.fn();
const chart = Object.assign(Object.create(MoexChart.prototype), {
chart: mainChart,
seasonalityManager,
eventManager,
headerRenderer,
drawingToolbarRenderer: { renderComponent: jest.fn() },
toolbarRenderer: undefined,
controlBarRenderer: undefined,
footerRenderer: undefined,
modalRenderer: {},
fullscreen: {},
setChartView,
}) as MoexChart;
return {
chart,
mainChart,
seasonalityManager,
eventManager,
headerRenderer,
setChartView,
};
}
function renderHeader({
showSnapshotButton,
showSeasonalityButton,
seasonalityActive = false,
}: HeaderTestOptions = {}) {
const context = createTestContext();
context.seasonalityManager.isActive.mockReturnValue(seasonalityActive);
const config = {
chartCollectionPreset: {
supportedTimeframes: [],
supportedChartSeriesTypes: [],
showSettingsButton: false,
showMenuButton: false,
showFullscreenButton: false,
undoRedoEnabled: false,
showCompareButton: false,
showSnapshotButton,
showSeasonalityButton,
theme: 'tr',
},
} as unknown as IMoexChart;
const internals = context.chart as unknown as {
renderAttachments: (config: IMoexChart, toggleToolbar: () => boolean) => void;
};
internals.renderAttachments(
config,
jest.fn(() => false),
);
const header = context.headerRenderer.renderComponent.mock.calls[0][0] as ReactElement<HeaderTestProps>;
return { ...context, header };
}
describe('MoexChart', () => {
it('должен выйти из сезонности перед очисткой основного инструмента', () => {
const { chart, eventManager, seasonalityManager } = createTestContext();
chart.clearSymbol();
expect(seasonalityManager.back).toHaveBeenCalledTimes(1);
expect(eventManager.clearSymbol).toHaveBeenCalledTimes(1);
expect(seasonalityManager.back.mock.invocationCallOrder[0]).toBeLessThan(
eventManager.clearSymbol.mock.invocationCallOrder[0],
);
});
it('должен выйти из сезонности перед установкой основного инструмента', () => {
const { chart, eventManager, seasonalityManager } = createTestContext();
const symbol = {
symbolId: 'SBER',
symbol: 'SBER',
symbolName: 'Sberbank',
};
chart.setSymbol(symbol);
expect(seasonalityManager.back).toHaveBeenCalledTimes(1);
expect(eventManager.setSymbol).toHaveBeenCalledTimes(1);
expect(eventManager.setSymbol).toHaveBeenCalledWith(symbol);
expect(seasonalityManager.back.mock.invocationCallOrder[0]).toBeLessThan(
eventManager.setSymbol.mock.invocationCallOrder[0],
);
});
it('должен копировать основной график, если сезонность неактивна', async () => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton: true,
});
expect(header.props.copyImage).toEqual(expect.any(Function));
await header.props.copyImage?.();
expect(mainChart.copyImage).toHaveBeenCalledTimes(1);
expect(seasonalityManager.copyImage).not.toHaveBeenCalled();
});
it('должен копировать сезонность, если она активна', async () => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton: true,
seasonalityActive: true,
});
expect(header.props.copyImage).toEqual(expect.any(Function));
await header.props.copyImage?.();
expect(seasonalityManager.copyImage).toHaveBeenCalledTimes(1);
expect(mainChart.copyImage).not.toHaveBeenCalled();
});
it.each([undefined, false])('не должен передавать copyImage при showSnapshotButton=%s', (showSnapshotButton) => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton,
});
expect(header.props.copyImage).toBeUndefined();
expect(mainChart.copyImage).not.toHaveBeenCalled();
expect(seasonalityManager.copyImage).not.toHaveBeenCalled();
});
it('должен передавать переключение вида при включённой сезонности', () => {
const { header, setChartView } = renderHeader({
showSeasonalityButton: true,
});
expect(header.props.setChartView).toBe(setChartView);
});
it.each([undefined, false])(
'не должен передавать переключение вида при showSeasonalityButton=%s',
(showSeasonalityButton) => {
const { header } = renderHeader({ showSeasonalityButton });
expect(header.props.setChartView).toBeUndefined();
},
);
it('должен передавать состояние сезонности в Header', () => {
const { header, seasonalityManager } = renderHeader();
expect(seasonalityManager.getState).toHaveBeenCalledTimes(1);
expect(header.props.seasonalityStateObs).toBe(EMPTY);
});
});
import { EMPTY } from 'rxjs';
import { type IMoexChart, MoexChart } from '@core/MoexChart';
import type { ReactElement } from 'react';
interface HeaderTestProps {
copyImage?: () => Promise<void>;
setChartView?: (view: unknown) => void;
seasonalityStateObs: typeof EMPTY;
}
interface HeaderTestOptions {
showSnapshotButton?: boolean;
showSeasonalityButton?: boolean;
seasonalityActive?: boolean;
}
function createTestContext() {
const drawingsCollectionManager = {
selectedDrawing: jest.fn(),
updateSelectedDrawingSettings: jest.fn(),
toggleSelectedDrawingLock: jest.fn(),
openSelectedDrawingSettings: jest.fn(),
deleteSelectedDrawing: jest.fn(),
};
const mainChart = {
copyImage: jest.fn().mockResolvedValue(undefined),
getDrawingsCollectionManager: () => drawingsCollectionManager,
};
const seasonalityManager = {
back: jest.fn(),
getState: jest.fn(() => EMPTY),
isActive: jest.fn(() => false),
copyImage: jest.fn().mockResolvedValue(undefined),
};
const eventManager = {
clearSymbol: jest.fn(),
setSymbol: jest.fn(),
getTimeframeObs: jest.fn(),
setTimeframe: jest.fn(),
getSelectedSeries: jest.fn(),
setSeriesSelected: jest.fn(),
};
const headerRenderer = { renderComponent: jest.fn() };
const setChartView = jest.fn();
const chart = Object.assign(Object.create(MoexChart.prototype), {
chart: mainChart,
seasonalityManager,
eventManager,
headerRenderer,
drawingToolbarRenderer: { renderComponent: jest.fn() },
toolbarRenderer: undefined,
controlBarRenderer: undefined,
footerRenderer: undefined,
modalRenderer: {},
fullscreen: {},
setChartView,
}) as MoexChart;
return {
chart,
mainChart,
seasonalityManager,
eventManager,
headerRenderer,
setChartView,
};
}
function renderHeader({
showSnapshotButton,
showSeasonalityButton,
seasonalityActive = false,
}: HeaderTestOptions = {}) {
const context = createTestContext();
context.seasonalityManager.isActive.mockReturnValue(seasonalityActive);
const config = {
chartCollectionPreset: {
supportedTimeframes: [],
supportedChartSeriesTypes: [],
showSettingsButton: false,
showMenuButton: false,
showFullscreenButton: false,
undoRedoEnabled: false,
showCompareButton: false,
showSnapshotButton,
showSeasonalityButton,
theme: 'tr',
},
} as unknown as IMoexChart;
const internals = context.chart as unknown as {
renderAttachments: (config: IMoexChart, toggleToolbar: () => boolean) => void;
};
internals.renderAttachments(
config,
jest.fn(() => false),
);
const header = context.headerRenderer.renderComponent.mock.calls[0][0] as ReactElement<HeaderTestProps>;
return { ...context, header };
}
describe('MoexChart', () => {
it('должен выйти из сезонности перед очисткой основного инструмента', () => {
const { chart, eventManager, seasonalityManager } = createTestContext();
chart.clearSymbol();
expect(seasonalityManager.back).toHaveBeenCalledTimes(1);
expect(eventManager.clearSymbol).toHaveBeenCalledTimes(1);
expect(seasonalityManager.back.mock.invocationCallOrder[0]).toBeLessThan(
eventManager.clearSymbol.mock.invocationCallOrder[0],
);
});
it('должен выйти из сезонности перед установкой основного инструмента', () => {
const { chart, eventManager, seasonalityManager } = createTestContext();
const symbol = {
symbolId: 'SBER',
symbol: 'SBER',
symbolName: 'Sberbank',
};
chart.setSymbol(symbol);
expect(seasonalityManager.back).toHaveBeenCalledTimes(1);
expect(eventManager.setSymbol).toHaveBeenCalledTimes(1);
expect(eventManager.setSymbol).toHaveBeenCalledWith(symbol);
expect(seasonalityManager.back.mock.invocationCallOrder[0]).toBeLessThan(
eventManager.setSymbol.mock.invocationCallOrder[0],
);
});
it('должен копировать основной график, если сезонность неактивна', async () => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton: true,
});
expect(header.props.copyImage).toEqual(expect.any(Function));
await header.props.copyImage?.();
expect(mainChart.copyImage).toHaveBeenCalledTimes(1);
expect(seasonalityManager.copyImage).not.toHaveBeenCalled();
});
it('должен копировать сезонность, если она активна', async () => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton: true,
seasonalityActive: true,
});
expect(header.props.copyImage).toEqual(expect.any(Function));
await header.props.copyImage?.();
expect(seasonalityManager.copyImage).toHaveBeenCalledTimes(1);
expect(mainChart.copyImage).not.toHaveBeenCalled();
});
it.each([undefined, false])('не должен передавать copyImage при showSnapshotButton=%s', (showSnapshotButton) => {
const { header, mainChart, seasonalityManager } = renderHeader({
showSnapshotButton,
});
expect(header.props.copyImage).toBeUndefined();
expect(mainChart.copyImage).not.toHaveBeenCalled();
expect(seasonalityManager.copyImage).not.toHaveBeenCalled();
});
it('должен передавать переключение вида при включённой сезонности', () => {
const { header, setChartView } = renderHeader({
showSeasonalityButton: true,
});
expect(header.props.setChartView).toBe(setChartView);
});
it.each([undefined, false])(
'не должен передавать переключение вида при showSeasonalityButton=%s',
(showSeasonalityButton) => {
const { header } = renderHeader({ showSeasonalityButton });
expect(header.props.setChartView).toBeUndefined();
},
);
it('должен передавать состояние сезонности в Header', () => {
const { header, seasonalityManager } = renderHeader();
expect(seasonalityManager.getState).toHaveBeenCalledTimes(1);
expect(header.props.seasonalityStateObs).toBe(EMPTY);
});
});