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


import { BehaviorSubject } from 'rxjs';

import type { BaseDrawingParams } from '@core/Drawings/SeriesDrawingBase';
import type { CanvasRenderingTarget2D } from 'fancy-canvas';
import type { Coordinate, Logical, Time } from 'lightweight-charts';

interface EnvironmentOptions {
  selected?: boolean;
  locked?: boolean;
  seriesData?: readonly unknown[];
  width?: number;
  height?: number;
}

export function createDrawingTestEnvironment({
  selected = false,
  locked = false,
  seriesData = [],
  width = 400,
  height = 300,
}: EnvironmentOptions = {}) {
  let selectedValue = selected;
  let lockedValue = locked;

  const selected$ = new BehaviorSubject(selected);
  const locked$ = new BehaviorSubject(locked);

  const interaction = {
    selected$,
    locked$,
    isSelected: jest.fn(() => selectedValue),
    isLocked: jest.fn(() => lockedValue),
    select: jest.fn(() => {
      selectedValue = true;
      selected$.next(true);
    }),
    deselect: jest.fn(() => {
      selectedValue = false;
      selected$.next(false);
    }),
  } as unknown as BaseDrawingParams['interaction'];

  const container = document.createElement('div');

  jest.spyOn(container, 'getBoundingClientRect').mockReturnValue({
    x: 0,
    y: 0,
    left: 0,
    top: 0,
    right: width,
    bottom: height,
    width,
    height,
    toJSON: jest.fn(),
  });

  const coordinateToTime = jest.fn((coordinate: Coordinate): Time | null => Number(coordinate) as Time);
  const timeToCoordinate = jest.fn((time: Time): Coordinate | null => Number(time) as Coordinate);
  const coordinateToLogical = jest.fn((coordinate: Coordinate): Logical | null => Number(coordinate) as Logical);
  const logicalToCoordinate = jest.fn((logical: Logical): Coordinate | null => Number(logical) as Coordinate);

  const timeScale = {
    coordinateToTime,
    timeToCoordinate,
    coordinateToLogical,
    logicalToCoordinate,
    getVisibleLogicalRange: jest.fn(() => ({
      from: 0 as Logical,
      to: Math.max(seriesData.length - 1, 0) as Logical,
    })),
    subscribeVisibleLogicalRangeChange: jest.fn(),
    unsubscribeVisibleLogicalRangeChange: jest.fn(),
  };

  const pane = {
    paneIndex: jest.fn(() => 0),
    getHeight: jest.fn(() => height),
  };

  const chart = {
    timeScale: jest.fn(() => timeScale),
    applyOptions: jest.fn(),
    panes: jest.fn(() => [pane]),
    subscribeClick: jest.fn(),
    unsubscribeClick: jest.fn(),
    subscribeCrosshairMove: jest.fn(),
    unsubscribeCrosshairMove: jest.fn(),
  } as unknown as BaseDrawingParams['chart'];

  const coordinateToPrice = jest.fn((coordinate: Coordinate): number | null => Number(coordinate));
  const priceToCoordinate = jest.fn((price: number): Coordinate | null => price as Coordinate);

  const series = {
    coordinateToPrice,
    priceToCoordinate,
    data: jest.fn(() => seriesData),
    attachPrimitive: jest.fn(),
    detachPrimitive: jest.fn(),
    getPane: jest.fn(() => pane),
  } as unknown as BaseDrawingParams['series'];

  return {
    chart,
    series,
    container,
    interaction,
    timeScale,
    pane,
    coordinateToPrice,
    priceToCoordinate,
    setSelected(value: boolean) {
      selectedValue = value;
      selected$.next(value);
    },
    setLocked(value: boolean) {
      lockedValue = value;
      locked$.next(value);
    },
  };
}

export function createPointerEvent(
  target: Node,
  x: number,
  y: number,
  pointerId = 1,
  button = 0,
): PointerEvent {
  return {
    target,
    clientX: x,
    clientY: y,
    pointerId,
    button,
    preventDefault: jest.fn(),
    stopPropagation: jest.fn(),
  } as unknown as PointerEvent;
}

export function createMouseEvent(target: Node, x: number, y: number, detail = 1): MouseEvent {
  return {
    target,
    clientX: x,
    clientY: y,
    detail,
    preventDefault: jest.fn(),
    stopPropagation: jest.fn(),
  } as unknown as MouseEvent;
}

export function createCanvasTarget() {
  const context = {
    canvas: {
      width: 800,
      height: 600,
    },
    save: jest.fn(),
    restore: jest.fn(),
    beginPath: jest.fn(),
    closePath: jest.fn(),
    moveTo: jest.fn(),
    lineTo: jest.fn(),
    quadraticCurveTo: jest.fn(),
    arc: jest.fn(),
    arcTo: jest.fn(),
    rect: jest.fn(),
    clip: jest.fn(),
    fill: jest.fn(),
    stroke: jest.fn(),
    fillRect: jest.fn(),
    strokeRect: jest.fn(),
    fillText: jest.fn(),
    setLineDash: jest.fn(),
    translate: jest.fn(),
    rotate: jest.fn(),
    measureText: jest.fn((text: string) => ({
      width: text.length * 8,
    })),
    fillStyle: '',
    strokeStyle: '',
    lineWidth: 0,
    globalAlpha: 1,
    font: '',
    textAlign: 'start',
    textBaseline: 'alphabetic',
  };

  const useBitmapCoordinateSpace = jest.fn(
    (
      callback: (scope: {
        context: CanvasRenderingContext2D;
        horizontalPixelRatio: number;
        verticalPixelRatio: number;
        bitmapSize: {
          width: number;
          height: number;
        };
      }) => void,
    ) => {
      callback({
        context: context as unknown as CanvasRenderingContext2D,
        horizontalPixelRatio: 2,
        verticalPixelRatio: 2,
        bitmapSize: {
          width: 800,
          height: 600,
        },
      });
    },
  );

  return {
    context,
    useBitmapCoordinateSpace,
    target: {
      useBitmapCoordinateSpace,
    } as unknown as CanvasRenderingTarget2D,
  };
}