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


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,
     },
   });
 };