Загрузка данных
import { act, renderHook } from '@testing-library/react';
import { useDispatch } from 'react-redux';
import { communicator } from '@core/comm';
import { useAppSelect } from '@hooks/useAppSelector';
import { CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT, HIGHLIGHT_WIDGET_EVENT } from '@modules/widgets/shared';
import { addContentPropsToWidget, unbindWidgets } from '@store/slices/widgets';
import { getState } from '@store/store';
import { useWidgetsBind } from '@utils/hooks/useWidgetsBind';
import useChartComponentFacade from '../hooks/useChartComponentFacade';
import { useChartPublicContext } from '../hooks/useChartPublicContext';
import type { WidgetProperties } from '../properties/types';
import type { ChartContainerProps } from '../types';
import type { SymbolInfoInput } from 'moex-chart';
jest.mock('@store/store', () => ({
getState: jest.fn(),
}));
jest.mock('@store/slices/widgets', () => ({
addContentPropsToWidget: jest.fn(),
unbindWidgets: jest.fn(),
}));
jest.mock('react-redux', () => ({
useDispatch: jest.fn(),
}));
jest.mock('@core/comm', () => ({
communicator: {
listen: jest.fn(),
},
}));
jest.mock('@hooks/useAppSelector', () => ({
useAppSelect: jest.fn(),
}));
jest.mock('@utils/hooks/useWidgetsBind', () => ({
useWidgetsBind: jest.fn(),
}));
jest.mock('../hooks/useChartPublicContext', () => ({
useChartPublicContext: jest.fn(),
}));
interface PublicContextMockParams {
setCurrInstrument: (symbolInfo: SymbolInfoInput) => void;
}
describe('useChartComponentFacade', () => {
const mockUseDispatch = useDispatch as jest.Mock;
const mockUseAppSelect = useAppSelect as jest.Mock;
const mockAddContentPropsToWidget = addContentPropsToWidget as unknown as jest.Mock;
const mockUnbindWidgets = unbindWidgets as unknown as jest.Mock;
const mockGetState = getState as jest.Mock;
const mockUseWidgetsBind = useWidgetsBind as jest.Mock;
const mockUseChartPublicContext = useChartPublicContext as jest.Mock;
const mockCommunicatorListen = communicator.listen as jest.Mock;
const mockDispatch = jest.fn();
const mockTriggerRelatedWidgetsToUpdate = jest.fn();
const mockGetMasterInstrumentFromPublicContext = jest.fn();
const mockUnsubscribeCorpActions = jest.fn();
const mockUnsubscribeHighlighter = jest.fn();
const widgetProperties = {
chartState: {
savedInstrument: 'MOEX:SBER',
savedSymbol: 'SBER',
savedSymbolName: 'Сбербанк',
interval: '1m',
},
indicativeData: {
id: 1,
key: 'INDICATIVE',
secId: 'INAV',
instrumentName: 'Индикатив',
settlement: 'Расчётный',
firmName: 'Тестовая фирма',
},
} as WidgetProperties;
const renderFacade = () =>
renderHook(() =>
useChartComponentFacade({
widgetId: 42,
} as ChartContainerProps),
);
beforeEach(() => {
jest.clearAllMocks();
mockUseDispatch.mockReturnValue(mockDispatch);
mockUseAppSelect.mockReturnValue(widgetProperties);
mockGetState.mockReturnValue({
widgets: {
widgets: [
{
id: 42,
widgetContentProps: widgetProperties,
},
],
},
});
mockUseWidgetsBind.mockReturnValue({
triggerRelatedWidgetsToUpdate: mockTriggerRelatedWidgetsToUpdate,
getMasterInstrumentFromPublicContext: mockGetMasterInstrumentFromPublicContext,
});
mockUseChartPublicContext.mockReturnValue({
issKey: undefined,
});
mockAddContentPropsToWidget.mockImplementation((payload) => ({
type: 'widgets/addContentPropsToWidget',
payload,
}));
mockUnbindWidgets.mockImplementation((payload) => ({
type: 'widgets/unbindWidgets',
payload,
}));
mockCommunicatorListen.mockImplementation(({ messageType }, listener) => {
if (messageType === CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT) {
return mockUnsubscribeCorpActions;
}
if (messageType === HIGHLIGHT_WIDGET_EVENT) {
return mockUnsubscribeHighlighter;
}
return jest.fn();
});
});
it('should initialize symbol info from widget properties', () => {
// Arrange & Act
const { result } = renderFacade();
// Assert
expect(result.current.currentSymbolInfo).toEqual({
symbolId: 'MOEX:SBER',
symbol: 'SBER',
symbolName: 'Сбербанк',
});
});
it('should update and save instrument selected from modal', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.addInstrumentFromModal([
{
issKey: 'MOEX:GAZP',
displayName: 'Газпром',
symbol: 'GAZP',
},
]);
});
// Assert
expect(result.current.currentSymbolInfo).toEqual({
symbolId: 'MOEX:GAZP',
symbol: 'GAZP',
symbolName: 'Газпром',
});
expect(mockUnbindWidgets).toHaveBeenCalledWith({
widgetId: 42,
});
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith({
id: 42,
withoutSend: true,
widgetContentProps: {
chartState: {
...widgetProperties.chartState,
savedInstrument: 'MOEX:GAZP',
savedSymbol: 'GAZP',
savedSymbolName: 'Газпром',
},
indicativeData: undefined,
},
});
expect(mockTriggerRelatedWidgetsToUpdate).toHaveBeenCalledWith('MOEX:GAZP');
});
it('should update symbol metadata without unbinding when symbol id is unchanged', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.addInstrumentFromModal([
{
issKey: 'MOEX:SBER',
displayName: 'Сбербанк ПАО',
symbol: 'SBERP',
},
]);
});
// Assert
expect(result.current.currentSymbolInfo).toEqual({
symbolId: 'MOEX:SBER',
symbol: 'SBERP',
symbolName: 'Сбербанк ПАО',
});
expect(mockUnbindWidgets).not.toHaveBeenCalled();
expect(mockTriggerRelatedWidgetsToUpdate).not.toHaveBeenCalled();
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith({
id: 42,
withoutSend: true,
widgetContentProps: {
chartState: {
...widgetProperties.chartState,
savedInstrument: 'MOEX:SBER',
savedSymbol: 'SBERP',
savedSymbolName: 'Сбербанк ПАО',
},
indicativeData: widgetProperties.indicativeData,
},
});
});
it('should send dropped instrument update immediately', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.onDropInstrument('MOEX:LKOH', true);
});
// Assert
expect(result.current.currentSymbolInfo).toEqual({
symbolId: 'MOEX:LKOH',
symbol: undefined,
symbolName: undefined,
});
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
id: 42,
withoutSend: false,
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:LKOH',
savedSymbol: undefined,
savedSymbolName: undefined,
}),
}),
}),
);
});
it('should update instrument from public context without unbinding widget', () => {
// Arrange
renderFacade();
const publicContextParams = mockUseChartPublicContext.mock.calls[0]?.[0] as PublicContextMockParams;
jest.clearAllMocks();
// Act
act(() => {
publicContextParams.setCurrInstrument({
symbolId: 'MOEX:ROSN',
symbol: 'ROSN',
symbolName: 'Роснефть',
});
});
// Assert
expect(mockUnbindWidgets).not.toHaveBeenCalled();
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
withoutSend: false,
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:ROSN',
savedSymbol: 'ROSN',
savedSymbolName: 'Роснефть',
}),
}),
}),
);
});
it('should delegate missing symbol metadata fallback to moex chart', () => {
// Arrange
const { result } = renderFacade();
const corpActionsListener = mockCommunicatorListen.mock.calls.find(
([options]) => options.messageType === CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT,
)?.[1] as (message: Record<number, string>) => void;
// Act
act(() => {
corpActionsListener({
42: 'MOEX:UNKNOWN',
});
});
// Assert
expect(result.current.currentSymbolInfo).toEqual({
symbolId: 'MOEX:UNKNOWN',
symbol: undefined,
symbolName: undefined,
});
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:UNKNOWN',
savedSymbol: undefined,
savedSymbolName: undefined,
}),
}),
}),
);
});
it('should not update instrument when modal selection is empty', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.addInstrumentFromModal([]);
});
// Assert
expect(mockAddContentPropsToWidget).not.toHaveBeenCalled();
expect(mockUnbindWidgets).not.toHaveBeenCalled();
expect(mockTriggerRelatedWidgetsToUpdate).not.toHaveBeenCalled();
});
it('should update widget highlight state', () => {
// Arrange
const { result } = renderFacade();
const highlighterListener = mockCommunicatorListen.mock.calls.find(
([options]) => options.messageType === HIGHLIGHT_WIDGET_EVENT,
)?.[1] as (message: Record<number, boolean>) => void;
// Act
act(() => {
highlighterListener({
42: true,
});
});
// Assert
expect(result.current.isOver).toBe(true);
});
it('should unsubscribe communicator listeners on unmount', () => {
// Arrange
const { unmount } = renderFacade();
// Act
unmount();
// Assert
expect(mockUnsubscribeCorpActions).toHaveBeenCalledTimes(1);
expect(mockUnsubscribeHighlighter).toHaveBeenCalledTimes(1);
});
});