Загрузка данных
import { IChartApi, PriceScaleMode, SeriesType } from 'lightweight-charts';
import { BehaviorSubject } from 'rxjs';
import { DataSource } from '@core/DataSource';
import { EventManager } from '@core/EventManager';
import { Indicator } from '@core/Indicator';
import { IndicatorManager } from '@core/IndicatorManager';
import { Pane } from '@core/Pane';
import { PaneManager } from '@core/PaneManager';
import { PriceScale } from '@core/PriceScale';
import { CompareManager } from '@src/core/CompareManager';
import { CompareMode, Direction } from '@src/types';
import { PriceScaleSide } from '@src/types/snapshot';
import { Timeframes } from '@src/types/timeframes';
jest.mock('@core/Indicator');
type IndicatorFactory = (
zIndex: number,
moveUp: (id: string) => void,
moveDown: (id: string) => void,
) => Indicator;
describe('CompareManager', () => {
const IndicatorMock = Indicator as jest.MockedClass<typeof Indicator>;
let manager: CompareManager;
let mainPane: Pane;
let secondaryPane: Pane;
let rightPriceScale: PriceScale;
let addEntity: jest.Mock;
let getIndicators: jest.Mock;
let removeEntity: jest.Mock;
let getMainPane: jest.Mock;
let getPaneById: jest.Mock;
let addPane: jest.Mock;
let setPriceScaleSideVisible: jest.Mock;
let invalidate: jest.Mock;
let waitUntilReady: jest.Mock;
beforeAll(() => {
Object.defineProperty(globalThis, 'crypto', {
configurable: true,
value: {
randomUUID: () => 'test-uuid',
},
});
});
beforeEach(() => {
IndicatorMock.mockImplementation(
(params) =>
({
id: params.id,
destroy: jest.fn(),
getConfig: jest.fn(() => params.config),
getIndicatorType: jest.fn(() => params.type),
}) as unknown as Indicator,
);
rightPriceScale = {
getMode: jest.fn(() => PriceScaleMode.Normal),
setMode: jest.fn(),
} as unknown as PriceScale;
mainPane = {
getId: jest.fn(() => 0),
getPriceScale: jest.fn((_side: PriceScaleSide) => rightPriceScale),
} as unknown as Pane;
secondaryPane = {
getId: jest.fn(() => 1),
} as unknown as Pane;
addEntity = jest.fn((factory: IndicatorFactory) => factory(1, jest.fn(), jest.fn()));
getIndicators = jest.fn(() => new BehaviorSubject<Map<string, Indicator>>(new Map()));
removeEntity = jest.fn();
getMainPane = jest.fn(() => mainPane);
getPaneById = jest.fn();
addPane = jest.fn(() => secondaryPane);
setPriceScaleSideVisible = jest.fn();
invalidate = jest.fn();
waitUntilReady = jest.fn((_symbolRaw: string) => Promise.resolve());
const indicatorManager = {
addEntity,
getIndicators,
removeEntity,
} as unknown as IndicatorManager;
const paneManager = {
getMainPane,
getPaneById,
addPane,
setPriceScaleSideVisible,
invalidate,
} as unknown as PaneManager;
const dataSource = {
waitUntilReady,
} as unknown as DataSource;
const eventManager = {
timeframe: jest.fn(() => new BehaviorSubject(Timeframes['10s'])),
} as unknown as EventManager;
manager = new CompareManager({
chart: {} as IChartApi,
eventManager,
dataSource,
indicatorManager,
paneManager,
});
});
afterEach(() => {
manager.destroy();
});
it('должен добавлять compare-серию типа Line', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
symbol: 'SBER',
symbolName: 'Sberbank',
},
CompareMode.Absolute,
);
expect(IndicatorMock).toHaveBeenCalledTimes(1);
const params = IndicatorMock.mock.calls[0][0];
expect(params.config.series[0]?.name).toBe('Line');
expect(params.config.series[0]?.id).toBe('compare-test-uuid');
expect(params.associatedPane).toBe(mainPane);
expect(waitUntilReady).toHaveBeenCalledWith('SBER');
expect(manager.getAllEntities()).toHaveLength(1);
});
it('не должен добавлять невалидный инструмент', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: '',
},
CompareMode.Absolute,
);
expect(addEntity).not.toHaveBeenCalled();
expect(manager.getAllEntities()).toHaveLength(0);
});
it('должен создавать отдельный pane для режима NewPane', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.NewPane,
);
expect(addPane).toHaveBeenCalledTimes(1);
expect(IndicatorMock.mock.calls[0][0].associatedPane).toBe(secondaryPane);
});
it('должен использовать существующий pane при восстановлении NewPane', async () => {
getPaneById.mockReturnValue(secondaryPane);
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.NewPane,
1,
);
expect(getPaneById).toHaveBeenCalledWith(1);
expect(addPane).not.toHaveBeenCalled();
});
it('не должен повторно добавлять одинаковый инструмент в одном режиме', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.Absolute,
);
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.Absolute,
);
expect(addEntity).toHaveBeenCalledTimes(1);
});
it('не должен добавлять NewScale если compare уже существует', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.Absolute,
);
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'GAZP',
},
CompareMode.NewScale,
);
expect(addEntity).toHaveBeenCalledTimes(1);
});
it('должен включать левую шкалу для NewScale', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.NewScale,
);
expect(setPriceScaleSideVisible).toHaveBeenCalledWith(Direction.Left, true);
});
it('должен удалять compare-сущность', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.Absolute,
);
const [entry] = manager.getAllEntities();
if (!entry) {
throw new Error('Compare-сущность не была добавлена');
}
manager.removeSymbol('SBER');
expect(removeEntity).toHaveBeenCalledWith(entry.entity);
expect(entry.entity.destroy).toHaveBeenCalledTimes(1);
expect(manager.getAllEntities()).toHaveLength(0);
});
it('должен очищать все compare-сущности', async () => {
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'SBER',
},
CompareMode.Absolute,
);
await manager.setSymbolMode(
'Line' as SeriesType,
{
symbolId: 'GAZP',
},
CompareMode.NewPane,
);
expect(manager.getAllEntities()).toHaveLength(2);
manager.clear();
expect(manager.getAllEntities()).toHaveLength(0);
expect(removeEntity).toHaveBeenCalledTimes(2);
});
});