diff --git a/packages/bot-web-ui/src/stores/__tests__/chart-store.spec.tsx b/packages/bot-web-ui/src/stores/__tests__/chart-store.spec.tsx new file mode 100644 index 000000000000..7ae11941d5be --- /dev/null +++ b/packages/bot-web-ui/src/stores/__tests__/chart-store.spec.tsx @@ -0,0 +1,158 @@ +import { ServerTime } from '@deriv/bot-skeleton'; +import { LocalStore } from '@deriv/shared'; +import { mockStore } from '@deriv/stores'; +import { TStores } from '@deriv/stores/types'; +import { mock_ws } from 'Utils/mock'; +import { mockDBotStore } from 'Stores/useDBotStore'; +import ChartStore, { g_subscribers_map } from '../chart-store'; +import 'Utils/mock/mock-local-storage'; + +window.Blockly = { + derivWorkspace: { + getAllBlocks: jest.fn(() => ({ + find: jest.fn(), + })), + }, +}; + +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({})); + +describe('ChartStore', () => { + const mock_store: TStores = mockStore({ + common: { + server_time: { + clone: jest.fn(() => ({ + unix: jest.fn(() => '2024-03-11T10:56:02.239Z'), + })), + }, + }, + }); + + let chartStore: ChartStore; + const mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + beforeEach(() => { + ServerTime.init(mock_store.common); + chartStore = new ChartStore(mock_DBot_store); + }); + + it('should initialize ChartStore with default values', () => { + expect(chartStore.symbol).toBeUndefined(); + expect(chartStore.is_chart_loading).toBeUndefined(); + expect(chartStore.chart_type).toBe('line'); + }); + + it('should return true when contracts exist and the first contract is ended', () => { + const mockContractStore = { + contracts: [{ is_ended: true }, { is_ended: false }], + }; + mock_DBot_store.transactions = mockContractStore; + const result = chartStore.is_contract_ended; + + expect(result).toBe(true); + }); + + it('should update symbol correctly', () => { + const mockWorkspace = { + getAllBlocks: jest.fn(() => [{ type: 'trade_definition_market', getFieldValue: jest.fn(() => 'EURUSD') }]), + }; + + window.Blockly.derivWorkspace = mockWorkspace; + chartStore.updateSymbol(); + + expect(chartStore.symbol).toEqual('EURUSD'); + }); + + it('should update granularity correctly', () => { + chartStore.updateGranularity(60); + expect(chartStore.granularity).toEqual(60); + }); + + it('should update chart type correctly', () => { + chartStore.updateChartType('candle'); + expect(chartStore.chart_type).toEqual('candle'); + }); + + it('should set chart status correctly', () => { + chartStore.setChartStatus(true); + expect(chartStore.is_chart_loading).toEqual(true); + }); + + it('should subscribe to ticks history', () => { + const req = { subscribe: 1 }; + const callback = jest.fn(); + chartStore.wsSubscribe(req, callback); + + expect(mock_DBot_store.ws.subscribeTicksHistory).toHaveBeenCalledWith(req, callback); + }); + + it('should forget ticks history', () => { + const subscribe_req = { subscribe: 1 }; + const callback = jest.fn(); + chartStore.wsSubscribe(subscribe_req, callback); + const key = JSON.stringify(subscribe_req); + + expect(g_subscribers_map).toHaveProperty(key); + chartStore.wsForget(subscribe_req); + expect(g_subscribers_map).not.toHaveProperty(key); + }); + + it('should forget stream', () => { + const stream_id = 'test_stream_id'; + chartStore.wsForgetStream(stream_id); + + expect(mock_DBot_store.ws.forgetStream).toHaveBeenCalledWith(stream_id); + }); + + it('should send request and return server time', async () => { + const req = { time: 1 }; + const result = await chartStore.wsSendRequest(req); + + expect(result.msg_type).toEqual('time'); + expect(result.time).toEqual('2024-03-11T10:56:02.239Z'); + }); + + it('should send request and return active symbols', async () => { + const req = { active_symbols: 1 }; + await chartStore.wsSendRequest(req); + + expect(mock_DBot_store.ws.activeSymbols).toHaveBeenCalledWith(); + }); + + it('should send request using storage.send', async () => { + const req = { storage: 'storage' }; + await chartStore.wsSendRequest(req); + + expect(mock_DBot_store.ws.storage.send).toHaveBeenCalledWith(req); + }); + + it('should get markets order', () => { + const active_symbols = [ + { market: 'EURUSD', display_name: 'EUR/USD' }, + { market: 'AUDUSD', display_name: 'AUD/USD' }, + { market: 'CADUSD', display_name: 'CAD/USD' }, + ]; + const marketsOrder = chartStore.getMarketsOrder(active_symbols); + + expect(marketsOrder).toEqual(['AUDUSD', 'CADUSD', 'EURUSD']); + }); + + it('should get markets order with synthetic index', () => { + const active_symbols = [{ market: 'synthetic_index', display_name: 'Synthetic Index' }]; + const marketsOrder = chartStore.getMarketsOrder(active_symbols); + + expect(marketsOrder).toEqual(['synthetic_index']); + }); + + it('should update symbol and save to local storage', () => { + const mockSymbol = 'USDJPY'; + chartStore.onSymbolChange(mockSymbol); + + expect(chartStore.symbol).toEqual(mockSymbol); + }); + + it('should call restoreFromStorage ', () => { + LocalStore.set('bot.chart_props', '{none}'); + chartStore.restoreFromStorage(); + }); +}); diff --git a/packages/bot-web-ui/src/stores/__tests__/toolbar-store.spec.tsx b/packages/bot-web-ui/src/stores/__tests__/toolbar-store.spec.tsx new file mode 100644 index 000000000000..ba41e59c2772 --- /dev/null +++ b/packages/bot-web-ui/src/stores/__tests__/toolbar-store.spec.tsx @@ -0,0 +1,101 @@ +import { mockStore } from '@deriv/stores'; +import { TStores } from '@deriv/stores/types'; +import { mock_ws } from 'Utils/mock'; +import { mockDBotStore } from 'Stores/useDBotStore'; +import ToolbarStore from '../toolbar-store'; + +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({})); + +const mock_undo = jest.fn(); +const mock_cleanup = jest.fn(); +const mock_zoom = jest.fn(); + +window.Blockly = { + derivWorkspace: { + undo: mock_undo, + hasRedoStack: jest.fn(), + hasUndoStack: jest.fn(), + cached_xml: { + main: 'main', + }, + getMetrics: jest.fn(() => ({ + viewWidth: 100, + viewHeight: 100, + })), + zoom: mock_zoom, + cleanUp: mock_cleanup, + }, + utils: { + genUid: jest.fn(), + }, + Events: { + setGroup: jest.fn(), + }, + svgResize: jest.fn(), +}; + +describe('ToolbarStore', () => { + const mock_store: TStores = mockStore({}); + const mock_DBot_store = mockDBotStore(mock_store, mock_ws); + let toolbarStore: ToolbarStore; + + beforeEach(() => { + toolbarStore = new ToolbarStore(mock_DBot_store); + }); + + it('should initialize ToolbarStore with default values', () => { + expect(toolbarStore.is_animation_info_modal_open).toBe(false); + expect(toolbarStore.is_dialog_open).toBe(false); + expect(toolbarStore.file_name).toBe('Untitled Bot'); + expect(toolbarStore.has_undo_stack).toBe(false); + expect(toolbarStore.has_redo_stack).toBe(false); + expect(toolbarStore.is_reset_button_clicked).toBe(false); + }); + + it('should show dialog on reset button click', () => { + toolbarStore.onResetClick(); + expect(toolbarStore.is_dialog_open).toBe(true); + }); + + it('should hide dialog on close reset dialog click', () => { + toolbarStore.closeResetDialog(); + expect(toolbarStore.is_dialog_open).toBe(false); + }); + + it('should not show dialog onResetOkButtonClick', () => { + toolbarStore.onResetOkButtonClick(); + expect(toolbarStore.is_dialog_open).toBe(false); + expect(toolbarStore.is_reset_button_clicked).toBe(false); + }); + + it('should show dialog onResetOkButtonClick while bot is running', () => { + mock_DBot_store.run_panel.setIsRunning(true); + toolbarStore.onResetOkButtonClick(); + expect(toolbarStore.is_reset_button_clicked).toBe(true); + }); + + it('should call reset default strategy', async () => { + await toolbarStore.resetDefaultStrategy(); + expect(window.Blockly.derivWorkspace.strategy_to_load).toBe('main'); + }); + + it('should call onSortClick', () => { + toolbarStore.onSortClick(); + expect(mock_cleanup).toHaveBeenCalled(); + }); + + it('should handle undo and redo click correctly', () => { + toolbarStore.onUndoClick(true); + expect(mock_undo).toHaveBeenCalledWith(true); + }); + + it('should call onZoomInOutClick ', () => { + toolbarStore.onZoomInOutClick(true); + expect(mock_zoom).toHaveBeenCalledWith(50, 50, 1); + }); + + it('should call onZoomInOutClick ', () => { + toolbarStore.onZoomInOutClick(false); + expect(mock_zoom).toHaveBeenCalledWith(50, 50, -1); + }); +}); diff --git a/packages/bot-web-ui/src/stores/chart-store.ts b/packages/bot-web-ui/src/stores/chart-store.ts index 23fa075f844f..cf9750d6a6e4 100644 --- a/packages/bot-web-ui/src/stores/chart-store.ts +++ b/packages/bot-web-ui/src/stores/chart-store.ts @@ -1,8 +1,5 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx'; -// import { tabs_title } from '../constants/bot-contents'; -import { ServerTime } from '@deriv/bot-skeleton'; -import { LocalStore } from '@deriv/shared'; -import RootStore from './root-store'; +// eslint-disable-next-line import/no-extraneous-dependencies import { ActiveSymbolsRequest, ServerTimeRequest, @@ -10,8 +7,11 @@ import { TicksStreamRequest, TradingTimesRequest, } from '@deriv/api-types'; +import { ServerTime } from '@deriv/bot-skeleton'; +import { LocalStore } from '@deriv/shared'; +import RootStore from './root-store'; -const g_subscribers_map: Partial>> = {}; +export const g_subscribers_map: Partial>> = {}; let WS: RootStore['ws']; export default class ChartStore { diff --git a/packages/bot-web-ui/src/stores/toolbar-store.ts b/packages/bot-web-ui/src/stores/toolbar-store.ts index 20f825897e52..3a20f08b72b9 100644 --- a/packages/bot-web-ui/src/stores/toolbar-store.ts +++ b/packages/bot-web-ui/src/stores/toolbar-store.ts @@ -19,7 +19,6 @@ interface IToolbarStore { setHasRedoStack: () => void; } -const Blockly = window.Blockly; export default class ToolbarStore implements IToolbarStore { root_store: any; @@ -79,8 +78,8 @@ export default class ToolbarStore implements IToolbarStore { }; resetDefaultStrategy = async () => { - const workspace = Blockly.derivWorkspace; - workspace.current_strategy_id = Blockly.utils.genUid(); + const workspace = window.Blockly.derivWorkspace; + workspace.current_strategy_id = window.Blockly.utils.genUid(); await load({ block_string: workspace.cached_xml.main, file_name: config.default_file_name, @@ -99,20 +98,20 @@ export default class ToolbarStore implements IToolbarStore { indentWorkspace: { x, y }, }, } = config; - Blockly.derivWorkspace.cleanUp(x, y); + window.Blockly.derivWorkspace.cleanUp(x, y); }; onUndoClick = (is_redo: boolean): void => { - Blockly.Events.setGroup('undo_clicked'); - Blockly.derivWorkspace.undo(is_redo); - Blockly.svgResize(Blockly.derivWorkspace); // Called for CommentDelete event. + window.Blockly.Events.setGroup('undo_clicked'); + window.Blockly.derivWorkspace.undo(is_redo); + window.Blockly.svgResize(window.Blockly.derivWorkspace); // Called for CommentDelete event. this.setHasRedoStack(); this.setHasUndoStack(); - Blockly.Events.setGroup(false); + window.Blockly.Events.setGroup(false); }; onZoomInOutClick = (is_zoom_in: boolean): void => { - const workspace = Blockly.derivWorkspace; + const workspace = window.Blockly.derivWorkspace; const metrics = workspace.getMetrics(); const addition = is_zoom_in ? 1 : -1; @@ -120,10 +119,10 @@ export default class ToolbarStore implements IToolbarStore { }; setHasUndoStack = (): void => { - this.has_undo_stack = Blockly.derivWorkspace?.hasUndoStack(); + this.has_undo_stack = window.Blockly.derivWorkspace?.hasUndoStack(); }; setHasRedoStack = (): void => { - this.has_redo_stack = Blockly.derivWorkspace?.hasRedoStack(); + this.has_redo_stack = window.Blockly.derivWorkspace?.hasRedoStack(); }; } diff --git a/packages/bot-web-ui/src/utils/mock/mock-local-storage.ts b/packages/bot-web-ui/src/utils/mock/mock-local-storage.ts new file mode 100644 index 000000000000..0c9ba996e7a6 --- /dev/null +++ b/packages/bot-web-ui/src/utils/mock/mock-local-storage.ts @@ -0,0 +1,21 @@ +// to use this localStorage mock, add the following import statement to the test file: +// import 'Utils/mock/mock-local-storage'; + +let store: Record = {}; + +export const mockLocalStorage = (() => { + return { + getItem(key: string) { + return store[key] || null; + }, + setItem(key: string, value: string) { + store[key] = value.toString(); + }, + clear() { + store = {}; + }, + removeItem(key: string) { + delete store[key]; + }, + }; +})(); diff --git a/packages/bot-web-ui/src/utils/mock/ws-mock.js b/packages/bot-web-ui/src/utils/mock/ws-mock.js index cca6b57a9861..253c21df0da2 100644 --- a/packages/bot-web-ui/src/utils/mock/ws-mock.js +++ b/packages/bot-web-ui/src/utils/mock/ws-mock.js @@ -8,7 +8,20 @@ export const mock_ws = { send: jest.fn(), }, contractUpdate: jest.fn(), - subscribeTicksHistory: jest.fn(), + subscribeTicksHistory: jest.fn((req, callback) => { + const subscriber = { + history: { + times: [], + prices: [], + }, + msg_type: 'history', + pip_size: 3, + req_id: 31, + unsubscribe: jest.fn(), + }; + callback(); + return subscriber; + }), forgetStream: jest.fn(), activeSymbols: jest.fn(), send: jest.fn(),