Загрузка данных
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,
}),
]);
}