Загрузка данных
diff --git a/src/widgets/Chart/__tests__/useMoexchart.test.tsx b/src/widgets/Chart/__tests__/useMoexchart.test.tsx
index d3f5f0eee7597600d7ff3dce88775a8d49124970..00642c354bc722927b0899b550def3116df637e4 100644
--- a/src/widgets/Chart/__tests__/useMoexchart.test.tsx
+++ b/src/widgets/Chart/__tests__/useMoexchart.test.tsx
@@ -111,6 +111,7 @@ interface MockMoexChartConfig {
interface MockMoexChartState {
initialInterval?: IntervalValue;
+ initialChartSeriesType?: string;
timeframe?: TimeframeValue;
savedData?: string;
}
@@ -819,6 +820,61 @@ describe('useMoexChart', () => {
expect(mockUpdateProperties).not.toHaveBeenCalled();
});
+ it('should create chart with initial chart series type', () => {
+ mockMoexChartState = {
+ timeframe: Timeframes['1d'],
+ initialInterval: Intervals['1Y'],
+ initialChartSeriesType: 'Line',
+ };
+
+ render(<TestComponent />);
+
+ expect(lastMoexChartConfig?.snapshot.charts[0]).toEqual(
+ expect.objectContaining({
+ timeframe: Timeframes['1d'],
+ interval: Intervals['1Y'],
+ chartSeriesType: 'Line',
+ }),
+ );
+ });
+
+ it('should keep configured chart series type when initial chart series type is not set', () => {
+ mockMoexChartState = {
+ timeframe: Timeframes['5m'],
+ savedData: JSON.stringify(mockSnapshot),
+ };
+
+ render(<TestComponent />);
+
+ expect(lastMoexChartConfig?.snapshot.charts[0].chartSeriesType).toBe('Candlestick');
+ });
+
+ it('should clear initial chart series type after chart creation', () => {
+ mockMoexChartState = {
+ timeframe: Timeframes['1d'],
+ initialChartSeriesType: 'Line',
+ };
+
+ render(<TestComponent />);
+
+ expect(mockUpdateProperties).toHaveBeenCalledTimes(1);
+
+ const updateCallback = mockUpdateProperties.mock.calls[0]?.[0] as UpdatePropertiesCallback;
+
+ const mockState: MockPropertiesState = {
+ moexChartState: {
+ timeframe: Timeframes['1d'],
+ initialInterval: Intervals['1Y'],
+ initialChartSeriesType: 'Line',
+ },
+ };
+
+ updateCallback(mockState);
+
+ expect(mockState.moexChartState?.initialChartSeriesType).toBeUndefined();
+ expect(mockState.moexChartState?.initialInterval).toBeUndefined();
+ });
+
it('should destroy chart on unmount', () => {
const { unmount } = render(<TestComponent />);
diff --git a/src/widgets/Chart/components/MoexChart/hooks/useMoexchart.ts b/src/widgets/Chart/components/MoexChart/hooks/useMoexchart.ts
index fe315c752d78ecb42b6897ab12e5d46ebb1e4dab..1e319b361c25f42003ea0f0cc52d91bee87b9b08 100644
--- a/src/widgets/Chart/components/MoexChart/hooks/useMoexchart.ts
+++ b/src/widgets/Chart/components/MoexChart/hooks/useMoexchart.ts
@@ -8,13 +8,19 @@ import { ChartIndicativeData } from '@widgets/Chart/types';
import { MOEX_CHART_CONFIG } from '../constants';
import { DataSourceProvider } from '../dataSourceProvide';
-import type { __CompareManager__, IMoexChart, SymbolInfoInput } from 'moex-chart';
+import type { __CompareManager__, ChartSeriesType, IMoexChart, SymbolInfoInput } from 'moex-chart';
interface TUseMoexChartProps {
symbolInfo: SymbolInfoInput;
indicativeData?: ChartIndicativeData;
}
+interface SnapshotSettings {
+ timeframe: Timeframes;
+ interval?: Intervals;
+ chartSeriesType?: ChartSeriesType;
+}
+
export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps) => {
const moexChartState = useSelectProperties(
(widgetProperties: Partial<WidgetProperties>) => widgetProperties.moexChartState,
@@ -33,6 +39,7 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
const timeframeRef = useRef<Timeframes | undefined>(moexChartState?.timeframe);
const savedDataRef = useRef<string | undefined>(moexChartState?.savedData);
const initialIntervalRef = useRef<Intervals | undefined>(moexChartState?.initialInterval);
+ const initialChartSeriesTypeRef = useRef<ChartSeriesType | undefined>(moexChartState?.initialChartSeriesType);
const updateTimeframeRef = useRef<((timeframe: Timeframes) => void) | null>(null);
useEffect(() => {
@@ -96,15 +103,15 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
const getSnapshotWithCurrentSettings = (
snapshot: typeof MOEX_CHART_CONFIG.snapshot | IMoexChart['snapshot'],
- timeframe: Timeframes,
- initialInterval?: Intervals,
+ { timeframe, interval, chartSeriesType }: SnapshotSettings,
): IMoexChart['snapshot'] => ({
...snapshot,
charts: snapshot.charts.map((chartSnapshot) => ({
...chartSnapshot,
...currentSymbolInfoRef.current,
timeframe,
- ...(initialInterval ? { interval: initialInterval } : {}),
+ ...(interval ? { interval } : {}),
+ ...(chartSeriesType ? { chartSeriesType } : {}),
})),
});
@@ -116,7 +123,7 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
const savedSnapshot = JSON.parse(savedDataRef.current) as IMoexChart['snapshot'];
const timeframe = timeframeRef.current ?? Timeframes['1m'];
- chartRef.current.setSnapshot(getSnapshotWithCurrentSettings(savedSnapshot, timeframe));
+ chartRef.current.setSnapshot(getSnapshotWithCurrentSettings(savedSnapshot, { timeframe }));
compareManagerRef.current = chartRef.current.getCompareManager();
};
@@ -129,6 +136,7 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
const timeframe = timeframeRef.current ?? Timeframes['1m'];
const initialInterval = initialIntervalRef.current;
+ const initialChartSeriesType = initialChartSeriesTypeRef.current;
const savedSnapshot = savedDataRef.current
? (JSON.parse(savedDataRef.current) as IMoexChart['snapshot'])
@@ -139,7 +147,11 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
const chart = new MoexChart({
...MOEX_CHART_CONFIG,
container,
- snapshot: getSnapshotWithCurrentSettings(savedSnapshot, timeframe, initialInterval),
+ snapshot: getSnapshotWithCurrentSettings(savedSnapshot, {
+ timeframe,
+ interval: initialInterval,
+ chartSeriesType: initialChartSeriesType,
+ }),
chartCollectionPreset: {
...MOEX_CHART_CONFIG.chartCollectionPreset,
openCompareModal: () => setIsCompareOpen(true),
@@ -160,13 +172,15 @@ export const useMoexChart = ({ symbolInfo, indicativeData }: TUseMoexChartProps)
chartRef.current = chart;
compareManagerRef.current = chart.getCompareManager();
- if (initialInterval) {
+ if (initialInterval || initialChartSeriesType) {
initialIntervalRef.current = undefined;
+ initialChartSeriesTypeRef.current = undefined;
updateProperties((state) => {
state.moexChartState = {
...state.moexChartState,
initialInterval: undefined,
+ initialChartSeriesType: undefined,
};
});
}
diff --git a/src/widgets/Chart/properties/types.ts b/src/widgets/Chart/properties/types.ts
index fc630e0dd74bee052f4777e8d8a758bcc8357f4c..db93374e19d4d6ce7bb291dc79198678a3f3886a 100644
--- a/src/widgets/Chart/properties/types.ts
+++ b/src/widgets/Chart/properties/types.ts
@@ -2,7 +2,7 @@ import type { ChartIndicativeData } from '../types';
import type { Contract } from '@modules/contracts/types';
import type { WidgetProperties as BaseWidgetProperties } from '@modules/widgetProperties/types';
-import type { Intervals, Timeframes } from 'moex-chart';
+import type { ChartSeriesType, Intervals, Timeframes } from 'moex-chart';
export interface WidgetProperties extends BaseWidgetProperties {
chartState: {
@@ -15,6 +15,7 @@ export interface WidgetProperties extends BaseWidgetProperties {
indicativeData?: ChartIndicativeData;
moexChartState?: {
initialInterval?: Intervals;
+ initialChartSeriesType?: ChartSeriesType;
timeframe?: Timeframes;
savedData?: string;
};
diff --git a/src/widgets/ntb/Indexes/components/IndexesTable/hooks/__tests__/useContextMenu.test.ts b/src/widgets/ntb/Indexes/components/IndexesTable/hooks/__tests__/useContextMenu.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1ed4d1c567ef4cdd5d65baeda7af011590e65565
--- /dev/null
+++ b/src/widgets/ntb/Indexes/components/IndexesTable/hooks/__tests__/useContextMenu.test.ts
@@ -0,0 +1,62 @@
+import { act, renderHook } from '@testing-library/react';
+
+import { openNtbChart } from '@widgets/ntb/shared/services/openChart';
+
+import { useContextMenu } from '../useContextMenu';
+
+import type { IndexesTableDataItem } from '../../types';
+import type { ContextMenuItem } from '@uikit/ContextMenu/types';
+import type { MouseEvent } from 'react';
+
+jest.mock('@widgets/ntb/shared/services/openChart', () => ({ openNtbChart: jest.fn() }));
+jest.mock('@widgets/ntb/Indexes/utils/downloadHistory', () => ({ downloadHistory: jest.fn() }));
+
+const mockedOpenNtbChart = openNtbChart as jest.Mock;
+
+const ntbIndex: IndexesTableDataItem = {
+ securityId: 'SUGVOL',
+ sourceId: 'DWH',
+ issKey: 'NTBVTFC:AGRO:SUGVOL',
+};
+
+const createContextMenuEvent = () =>
+ ({
+ preventDefault: jest.fn(),
+ clientX: 10,
+ clientY: 20,
+ }) as unknown as MouseEvent;
+
+const findItem = (items: ContextMenuItem[], key: string) =>
+ items.find((item) => item?.key === key) as { onClick?: () => void } | undefined;
+
+const renderContextMenu = (record: IndexesTableDataItem) => {
+ const { result } = renderHook(() => useContextMenu({ onShowHistory: jest.fn() }));
+
+ act(() => {
+ result.current.onContextMenu(createContextMenuEvent(), record);
+ });
+
+ return result;
+};
+
+describe('useContextMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should open chart for an instrument with iss key', () => {
+ const result = renderContextMenu(ntbIndex);
+
+ act(() => {
+ findItem(result.current.items, 'openChart')?.onClick?.();
+ });
+
+ expect(mockedOpenNtbChart).toHaveBeenCalledWith('NTBVTFC:AGRO:SUGVOL');
+ });
+
+ it('should not show chart item for an instrument without iss key', () => {
+ const result = renderContextMenu({ securityId: 'SUGVOL', sourceId: 'DWH' });
+
+ expect(findItem(result.current.items, 'openChart')).toBeUndefined();
+ });
+});
diff --git a/src/widgets/ntb/Logistic/LogisticContextMenu/__tests__/useContextMenu.test.ts b/src/widgets/ntb/Logistic/LogisticContextMenu/__tests__/useContextMenu.test.ts
index 1aa10f2caa14355532f1d732978caaa87a80ce8c..701d06ab2dffad2fcc3f9adf0c7e8bde7ad270bd 100644
--- a/src/widgets/ntb/Logistic/LogisticContextMenu/__tests__/useContextMenu.test.ts
+++ b/src/widgets/ntb/Logistic/LogisticContextMenu/__tests__/useContextMenu.test.ts
@@ -133,7 +133,7 @@ describe('useContextMenu', () => {
expect(mockedCreateGraphicWidget).toHaveBeenCalledWith({
chartState: { savedInstrument: 'NTBVTFC.NTBVLB.111222333' },
- moexChartState: { timeframe: '1d', initialInterval: '1Y' },
+ moexChartState: { timeframe: '1d', initialInterval: '1Y', initialChartSeriesType: 'Line' },
});
expect(mockProps.setOpenContextMenu).toHaveBeenCalledWith(false);
});
diff --git a/src/widgets/ntb/shared/services/__tests__/openChart.test.ts b/src/widgets/ntb/shared/services/__tests__/openChart.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4616313b7c75d836d15195427ca15a5a8ed4aa8d
--- /dev/null
+++ b/src/widgets/ntb/shared/services/__tests__/openChart.test.ts
@@ -0,0 +1,30 @@
+import { createGraphicWidget } from '@widgets/Chart/creator';
+
+import { openNtbChart } from '../openChart';
+
+jest.mock('@widgets/Chart/creator', () => ({
+ createGraphicWidget: jest.fn(),
+}));
+
+const mockedCreateGraphicWidget = createGraphicWidget as jest.Mock;
+
+describe('openNtbChart', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create graphic widget with one year interval, daily timeframe and line series', () => {
+ openNtbChart('NTBVTFC:AGRO:SUGVOL');
+
+ expect(mockedCreateGraphicWidget).toHaveBeenCalledWith({
+ chartState: { savedInstrument: 'NTBVTFC:AGRO:SUGVOL' },
+ moexChartState: { timeframe: '1d', initialInterval: '1Y', initialChartSeriesType: 'Line' },
+ });
+ });
+
+ it.each([null, undefined, ''])('should not create graphic widget for instrument id %p', (instrumentId) => {
+ openNtbChart(instrumentId);
+
+ expect(mockedCreateGraphicWidget).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/widgets/ntb/shared/services/openChart.ts b/src/widgets/ntb/shared/services/openChart.ts
index 98f6e94563df7bd4336bfc95c6329a86ac9344cf..80f7dca7ed0b61de8d3a21cdbafb464d476ba271 100644
--- a/src/widgets/ntb/shared/services/openChart.ts
+++ b/src/widgets/ntb/shared/services/openChart.ts
@@ -1,5 +1,9 @@
import { createGraphicWidget } from '@widgets/Chart/creator';
+import type { ChartSeriesType } from 'moex-chart';
+
+const NTB_CHART_SERIES_TYPE: ChartSeriesType = 'Line';
+
export const openNtbChart = (instrumentId: string | null | undefined) => {
if (!instrumentId) {
return;
@@ -12,6 +16,7 @@ export const openNtbChart = (instrumentId: string | null | undefined) => {
moexChartState: {
timeframe: '1d',
initialInterval: '1Y',
+ initialChartSeriesType: NTB_CHART_SERIES_TYPE,
},
});
};