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


import { BehaviorSubject, Subscription } from 'rxjs';

import { Chart } from '@core/Chart';
import { CHART_PANE_LEGEND_CONTAINER } from '@src/constants';
import { canvasToBlob, drawTextLayer } from '@src/utils';

jest.mock('@src/utils', () => ({
  ...jest.requireActual('@src/utils'),
  canvasToBlob: jest.fn(),
  drawTextLayer: jest.fn(),
}));

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;
    };
    takeScreenshot: jest.Mock;
    chartElement: jest.Mock;
  };
  subscriptions: Subscription;
  activeSymbolIds: string[];
  currentInterval: null;
  setupDataSourceSubs: () => void;
  copyImage: () => Promise<void>;
}

describe('Chart', () => {
  const drawTextLayerMock = drawTextLayer as jest.MockedFunction<typeof drawTextLayer>;
  const canvasToBlobMock = canvasToBlob as jest.MockedFunction<typeof canvasToBlob>;

  let clipboardWrite: jest.Mock;

  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,
        }),
        takeScreenshot: jest.fn(),
        chartElement: jest.fn(),
      },
      subscriptions: new Subscription(),
      activeSymbolIds: [],
      currentInterval: null,
    }) as ChartInternals;
  }

  beforeEach(() => {
    jest.clearAllMocks();

    clipboardWrite = jest.fn().mockResolvedValue(undefined);

    Object.defineProperty(navigator, 'clipboard', {
      configurable: true,
      value: { write: clipboardWrite },
    });

    Object.defineProperty(globalThis, 'ClipboardItem', {
      configurable: true,
      value: class {
        constructor(public data: Record<string, Blob>) {}
      },
    });
  });

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

    chart.setupDataSourceSubs();

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

    chart.subscriptions.unsubscribe();
  });

  describe('copyImage', () => {
    it('должен копировать изображение графика вместе с видимыми легендами', async () => {
      const chart = createChartInternals();
      const canvas = document.createElement('canvas');
      const chartElement = document.createElement('div');
      const visibleLegend = document.createElement('div');
      const hiddenLegend = document.createElement('div');
      const displayNoneLegend = document.createElement('div');
      const blob = new Blob(['chart'], { type: 'image/png' });

      visibleLegend.classList.add(CHART_PANE_LEGEND_CONTAINER);
      hiddenLegend.classList.add(CHART_PANE_LEGEND_CONTAINER);
      displayNoneLegend.classList.add(CHART_PANE_LEGEND_CONTAINER);

      hiddenLegend.hidden = true;
      displayNoneLegend.style.display = 'none';

      chartElement.append(visibleLegend, hiddenLegend, displayNoneLegend);

      chart.lwcChart.takeScreenshot.mockReturnValue(canvas);
      chart.lwcChart.chartElement.mockReturnValue(chartElement);
      canvasToBlobMock.mockResolvedValue(blob);

      await chart.copyImage();

      expect(chart.lwcChart.takeScreenshot).toHaveBeenCalledWith(true, false);
      expect(chart.lwcChart.chartElement).toHaveBeenCalledTimes(1);
      expect(drawTextLayerMock).toHaveBeenCalledTimes(1);
      expect(drawTextLayerMock).toHaveBeenCalledWith(canvas, visibleLegend, chartElement);
      expect(canvasToBlobMock).toHaveBeenCalledWith(canvas);
      expect(clipboardWrite).toHaveBeenCalledTimes(1);

      const items = clipboardWrite.mock.calls[0][0] as Array<{ data: Record<string, Blob> }>;

      expect(items[0].data).toEqual({
        'image/png': blob,
      });
    });

    it('не должен дорисовывать скрытые легенды', async () => {
      const chart = createChartInternals();
      const canvas = document.createElement('canvas');
      const chartElement = document.createElement('div');
      const hiddenLegend = document.createElement('div');

      hiddenLegend.classList.add(CHART_PANE_LEGEND_CONTAINER);
      hiddenLegend.hidden = true;

      chartElement.append(hiddenLegend);

      chart.lwcChart.takeScreenshot.mockReturnValue(canvas);
      chart.lwcChart.chartElement.mockReturnValue(chartElement);
      canvasToBlobMock.mockResolvedValue(new Blob(['chart'], { type: 'image/png' }));

      await chart.copyImage();

      expect(drawTextLayerMock).not.toHaveBeenCalled();
      expect(clipboardWrite).toHaveBeenCalledTimes(1);
    });

    it('не должен записывать изображение в clipboard, если Blob не удалось создать', async () => {
      const chart = createChartInternals();
      const canvas = document.createElement('canvas');
      const chartElement = document.createElement('div');

      chart.lwcChart.takeScreenshot.mockReturnValue(canvas);
      chart.lwcChart.chartElement.mockReturnValue(chartElement);
      canvasToBlobMock.mockRejectedValue(new Error('Failed to create chart image'));

      await expect(chart.copyImage()).rejects.toThrow('Failed to create chart image');

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


















const header = headerRenderer.renderComponent.mock.calls[0][0] as ReactElement<{
  copyImage: () => Promise<void>;
}>;

await header.props.copyImage();

expect(copyImage).toHaveBeenCalledTimes(1);














public async copyImage(): Promise<void> {
  const canvas = this.lwcChart.takeScreenshot(true, false);
  const chartElement = this.lwcChart.chartElement();

  chartElement.querySelectorAll<HTMLElement>(`.${CHART_PANE_LEGEND_CONTAINER}`).forEach((legend) => {
    if (legend.hidden || window.getComputedStyle(legend).display === 'none') {
      return;
    }

    drawTextLayer(canvas, legend, chartElement);
  });

  const blob = await canvasToBlob(canvas);

  await navigator.clipboard.write([
    new ClipboardItem({
      'image/png': blob,
    }),
  ]);
}