Загрузка данных


import { IChartApi } from 'lightweight-charts';
import { of } from 'rxjs';

import { DataSource } from '@core/DataSource';
import { DOMModel } from '@core/DOMModel';
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 { IndicatorsIds } from '@src/constants';

jest.mock('@core/Indicator');

jest.mock('@src/core/Indicators', () => ({
  indicatorsMap: () => ({
    ema: {
      newPane: false,
      label: 'EMA',
      series: [],
    },
  }),
}));

jest.mock('@src/utils', () => {
  const actual = jest.requireActual<typeof import('@src/utils')>('@src/utils');

  return {
    ...actual,
    applyNextIndicatorColors: (config: unknown) => config,
    getIndicatorColors: () => [],
  };
});

type EntityFactory = (
  zIndex: number,
  moveUp: (id: string) => void,
  moveDown: (id: string) => void,
) => Indicator;

describe('IndicatorManager', () => {
  const IndicatorMock = Indicator as jest.MockedClass<typeof Indicator>;

  let manager: IndicatorManager;
  let mainPane: Pane;

  let setEntity: jest.Mock;
  let removeEntity: jest.Mock;
  let getMainPane: jest.Mock;
  let getPaneById: jest.Mock;
  let addPane: jest.Mock;

  beforeEach(() => {
    IndicatorMock.mockImplementation(
      (params) =>
        ({
          id: params.id,
          destroy: jest.fn(),
          getConfig: jest.fn(() => params.config),
          getIndicatorType: jest.fn(() => params.type),
        }) as unknown as Indicator,
    );

    mainPane = {
      getId: jest.fn(() => 0),
    } as unknown as Pane;

    getMainPane = jest.fn(() => mainPane);
    getPaneById = jest.fn();
    addPane = jest.fn(() => mainPane);

    setEntity = jest.fn((factory: EntityFactory) => factory(1, jest.fn(), jest.fn()));
    removeEntity = jest.fn();

    const paneManager = {
      getMainPane,
      getPaneById,
      addPane,
    } as unknown as PaneManager;

    const eventManager = {
      symbolId: jest.fn(() => of('SBER')),
      symbol: jest.fn(() => of('SBER')),
    } as unknown as EventManager;

    const DOM = {
      setEntity,
      removeEntity,
    } as unknown as DOMModel;

    manager = new IndicatorManager({
      eventManager,
      dataSource: {} as DataSource,
      lwcChart: {} as IChartApi,
      paneManager,
      DOM,
    });
  });

  it('не должен добавлять индикатор без типа', () => {
    const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

    manager.addIndicator({});

    expect(setEntity).not.toHaveBeenCalled();
    expect(manager.getIndicators().value.size).toBe(0);

    consoleSpy.mockRestore();
  });

  it('должен добавлять индикатор', () => {
    manager.addIndicator({
      id: 'ema-1',
      indicatorType: IndicatorsIds.EMA,
    });

    expect(manager.getIndicators().value.has('ema-1')).toBe(true);
    expect(getMainPane).toHaveBeenCalledTimes(1);
    expect(IndicatorMock).toHaveBeenCalledTimes(1);
  });

  it('должен публиковать добавленные индикаторы', () => {
    let entities: Indicator[] = [];

    const subscription = manager.entities().subscribe((value) => {
      entities = value;
    });

    manager.addIndicator({
      id: 'ema-1',
      indicatorType: IndicatorsIds.EMA,
    });

    expect(entities).toHaveLength(1);
    expect(entities[0]).toBe(manager.getIndicators().value.get('ema-1'));

    subscription.unsubscribe();
  });

  it('должен использовать pane из snapshot', () => {
    const pane = {
      getId: jest.fn(() => 3),
    } as unknown as Pane;

    getPaneById.mockReturnValue(pane);

    manager.addIndicator({
      id: 'ema-1',
      indicatorType: IndicatorsIds.EMA,
      paneId: 3,
    });

    expect(getPaneById).toHaveBeenCalledWith(3);
    expect(addPane).not.toHaveBeenCalled();
  });

  it('должен создать pane если pane из snapshot отсутствует', () => {
    getPaneById.mockReturnValue(undefined);

    manager.addIndicator({
      id: 'ema-1',
      indicatorType: IndicatorsIds.EMA,
      paneId: 3,
    });

    expect(getPaneById).toHaveBeenCalledWith(3);
    expect(addPane).toHaveBeenCalledTimes(1);
  });

  it('должен удалять индикатор из состояния и DOM', () => {
    manager.addIndicator({
      id: 'ema-1',
      indicatorType: IndicatorsIds.EMA,
    });

    const indicator = manager.getIndicators().value.get('ema-1');

    if (!indicator) {
      throw new Error('Индикатор не был добавлен');
    }

    manager.deleteIndicator('ema-1');

    expect(manager.getIndicators().value.has('ema-1')).toBe(false);
    expect(removeEntity).toHaveBeenCalledWith(indicator);
    expect(indicator.destroy).toHaveBeenCalledTimes(1);
  });

  it('не должен удалять несуществующий индикатор', () => {
    manager.deleteIndicator('unknown');

    expect(removeEntity).not.toHaveBeenCalled();
  });
});
















import { BehaviorSubject, Subscription } from 'rxjs';

import { Chart } from '@core/Chart';

interface ChartInternals {
  eventManager: {
    symbolId: () => BehaviorSubject<string>;
    getInterval: () => BehaviorSubject<null>;
  };
  compareManager: {
    itemsObs: () => BehaviorSubject<{ symbolId: string }[]>;
  };
  dataSource: {
    setSymbols: jest.Mock;
    loadTill: jest.Mock;
  };
  lwcChart: {
    timeScale: () => {
      getVisibleRange: () => null;
    };
  };
  subscriptions: Subscription;
  activeSymbolIds: string[];
  currentInterval: null;
  setupDataSourceSubs: () => void;
}

describe('Chart', () => {
  function createChartInternals(): ChartInternals {
    return Object.assign(Object.create(Chart.prototype), {
      eventManager: {
        symbolId: () => new BehaviorSubject(''),
        getInterval: () => new BehaviorSubject(null),
      },
      compareManager: {
        itemsObs: () =>
          new BehaviorSubject([
            {
              symbolId: 'SBER',
            },
            {
              symbolId: 'SBER',
            },
          ]),
      },
      dataSource: {
        setSymbols: jest.fn(),
        loadTill: jest.fn(() => Promise.resolve()),
      },
      lwcChart: {
        timeScale: () => ({
          getVisibleRange: () => null,
        }),
      },
      subscriptions: new Subscription(),
      activeSymbolIds: [],
      currentInterval: null,
    }) as ChartInternals;
  }

  it('должен исключать пустой основной symbolId и удалять дубликаты', () => {
    const chart = createChartInternals();

    chart.setupDataSourceSubs();

    expect(chart.dataSource.setSymbols).toHaveBeenLastCalledWith(['SBER']);

    chart.subscriptions.unsubscribe();
  });
});