Загрузка данных
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';
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: (
instrumentId: string,
instrumentName?: string,
instrumentTicker?: string,
) => 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',
savedInstrumentName: 'Сбербанк',
savedInstrumentTicker: 'SBER',
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, display name and ticker from widget properties', () => {
// Arrange & Act
const { result } = renderFacade();
// Assert
expect(result.current.currentInstrument).toBe('MOEX:SBER');
expect(result.current.currentInstrumentName).toBe('Сбербанк');
expect(result.current.currentInstrumentTicker).toBe('SBER');
});
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.currentInstrument).toBe('MOEX:GAZP');
expect(result.current.currentInstrumentName).toBe('Газпром');
expect(result.current.currentInstrumentTicker).toBe('GAZP');
expect(mockUnbindWidgets).toHaveBeenCalledWith({
widgetId: 42,
});
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith({
id: 42,
withoutSend: true,
widgetContentProps: {
chartState: {
...widgetProperties.chartState,
savedInstrument: 'MOEX:GAZP',
savedInstrumentName: 'Газпром',
savedInstrumentTicker: 'GAZP',
},
indicativeData: undefined,
},
});
expect(mockTriggerRelatedWidgetsToUpdate).toHaveBeenCalledWith('MOEX:GAZP');
});
it('should update name and ticker without unbinding when symbol is unchanged', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.addInstrumentFromModal([
{
issKey: 'MOEX:SBER',
displayName: 'Сбербанк ПАО',
symbol: 'SBERP',
},
]);
});
// Assert
expect(result.current.currentInstrument).toBe('MOEX:SBER');
expect(result.current.currentInstrumentName).toBe('Сбербанк ПАО');
expect(result.current.currentInstrumentTicker).toBe('SBERP');
expect(mockUnbindWidgets).not.toHaveBeenCalled();
expect(mockTriggerRelatedWidgetsToUpdate).not.toHaveBeenCalled();
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith({
id: 42,
withoutSend: true,
widgetContentProps: {
chartState: {
...widgetProperties.chartState,
savedInstrument: 'MOEX:SBER',
savedInstrumentName: 'Сбербанк ПАО',
savedInstrumentTicker: 'SBERP',
},
indicativeData: widgetProperties.indicativeData,
},
});
});
it('should send dropped instrument update immediately', () => {
// Arrange
const { result } = renderFacade();
jest.clearAllMocks();
// Act
act(() => {
result.current.onDropInstruments(
{
issKey: 'MOEX:LKOH',
displayName: 'Лукойл',
symbol: 'LKOH',
},
true,
);
});
// Assert
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
id: 42,
withoutSend: false,
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:LKOH',
savedInstrumentName: 'Лукойл',
savedInstrumentTicker: 'LKOH',
}),
}),
}),
);
});
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('MOEX:ROSN', 'Роснефть', 'ROSN');
});
// Assert
expect(mockUnbindWidgets).not.toHaveBeenCalled();
expect(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
withoutSend: false,
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:ROSN',
savedInstrumentName: 'Роснефть',
savedInstrumentTicker: 'ROSN',
}),
}),
}),
);
});
it('should use ticker extracted from instrument id when metadata is unavailable', () => {
// Arrange
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(mockAddContentPropsToWidget).toHaveBeenCalledWith(
expect.objectContaining({
widgetContentProps: expect.objectContaining({
chartState: expect.objectContaining({
savedInstrument: 'MOEX:UNKNOWN',
savedInstrumentName: 'UNKNOWN',
savedInstrumentTicker: 'UNKNOWN',
}),
}),
}),
);
});
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);
});
});