Skip to content

Commit c6bf825

Browse files
authored
[BOT] Farabi/bot-1034/test coverage chart store and toolbar store (deriv-com#14109)
* chore: added test case for chart store and toolbar store * chore: 100% test coverage for chart store * chore: increased coverage for test cases * chore: full coverage of both stores * chore: use setIsRunning
1 parent ff15d9a commit c6bf825

File tree

6 files changed

+309
-17
lines changed

6 files changed

+309
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { ServerTime } from '@deriv/bot-skeleton';
2+
import { LocalStore } from '@deriv/shared';
3+
import { mockStore } from '@deriv/stores';
4+
import { TStores } from '@deriv/stores/types';
5+
import { mock_ws } from 'Utils/mock';
6+
import { mockDBotStore } from 'Stores/useDBotStore';
7+
import ChartStore, { g_subscribers_map } from '../chart-store';
8+
import 'Utils/mock/mock-local-storage';
9+
10+
window.Blockly = {
11+
derivWorkspace: {
12+
getAllBlocks: jest.fn(() => ({
13+
find: jest.fn(),
14+
})),
15+
},
16+
};
17+
18+
jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({}));
19+
20+
describe('ChartStore', () => {
21+
const mock_store: TStores = mockStore({
22+
common: {
23+
server_time: {
24+
clone: jest.fn(() => ({
25+
unix: jest.fn(() => '2024-03-11T10:56:02.239Z'),
26+
})),
27+
},
28+
},
29+
});
30+
31+
let chartStore: ChartStore;
32+
const mock_DBot_store = mockDBotStore(mock_store, mock_ws);
33+
34+
beforeEach(() => {
35+
ServerTime.init(mock_store.common);
36+
chartStore = new ChartStore(mock_DBot_store);
37+
});
38+
39+
it('should initialize ChartStore with default values', () => {
40+
expect(chartStore.symbol).toBeUndefined();
41+
expect(chartStore.is_chart_loading).toBeUndefined();
42+
expect(chartStore.chart_type).toBe('line');
43+
});
44+
45+
it('should return true when contracts exist and the first contract is ended', () => {
46+
const mockContractStore = {
47+
contracts: [{ is_ended: true }, { is_ended: false }],
48+
};
49+
mock_DBot_store.transactions = mockContractStore;
50+
const result = chartStore.is_contract_ended;
51+
52+
expect(result).toBe(true);
53+
});
54+
55+
it('should update symbol correctly', () => {
56+
const mockWorkspace = {
57+
getAllBlocks: jest.fn(() => [{ type: 'trade_definition_market', getFieldValue: jest.fn(() => 'EURUSD') }]),
58+
};
59+
60+
window.Blockly.derivWorkspace = mockWorkspace;
61+
chartStore.updateSymbol();
62+
63+
expect(chartStore.symbol).toEqual('EURUSD');
64+
});
65+
66+
it('should update granularity correctly', () => {
67+
chartStore.updateGranularity(60);
68+
expect(chartStore.granularity).toEqual(60);
69+
});
70+
71+
it('should update chart type correctly', () => {
72+
chartStore.updateChartType('candle');
73+
expect(chartStore.chart_type).toEqual('candle');
74+
});
75+
76+
it('should set chart status correctly', () => {
77+
chartStore.setChartStatus(true);
78+
expect(chartStore.is_chart_loading).toEqual(true);
79+
});
80+
81+
it('should subscribe to ticks history', () => {
82+
const req = { subscribe: 1 };
83+
const callback = jest.fn();
84+
chartStore.wsSubscribe(req, callback);
85+
86+
expect(mock_DBot_store.ws.subscribeTicksHistory).toHaveBeenCalledWith(req, callback);
87+
});
88+
89+
it('should forget ticks history', () => {
90+
const subscribe_req = { subscribe: 1 };
91+
const callback = jest.fn();
92+
chartStore.wsSubscribe(subscribe_req, callback);
93+
const key = JSON.stringify(subscribe_req);
94+
95+
expect(g_subscribers_map).toHaveProperty(key);
96+
chartStore.wsForget(subscribe_req);
97+
expect(g_subscribers_map).not.toHaveProperty(key);
98+
});
99+
100+
it('should forget stream', () => {
101+
const stream_id = 'test_stream_id';
102+
chartStore.wsForgetStream(stream_id);
103+
104+
expect(mock_DBot_store.ws.forgetStream).toHaveBeenCalledWith(stream_id);
105+
});
106+
107+
it('should send request and return server time', async () => {
108+
const req = { time: 1 };
109+
const result = await chartStore.wsSendRequest(req);
110+
111+
expect(result.msg_type).toEqual('time');
112+
expect(result.time).toEqual('2024-03-11T10:56:02.239Z');
113+
});
114+
115+
it('should send request and return active symbols', async () => {
116+
const req = { active_symbols: 1 };
117+
await chartStore.wsSendRequest(req);
118+
119+
expect(mock_DBot_store.ws.activeSymbols).toHaveBeenCalledWith();
120+
});
121+
122+
it('should send request using storage.send', async () => {
123+
const req = { storage: 'storage' };
124+
await chartStore.wsSendRequest(req);
125+
126+
expect(mock_DBot_store.ws.storage.send).toHaveBeenCalledWith(req);
127+
});
128+
129+
it('should get markets order', () => {
130+
const active_symbols = [
131+
{ market: 'EURUSD', display_name: 'EUR/USD' },
132+
{ market: 'AUDUSD', display_name: 'AUD/USD' },
133+
{ market: 'CADUSD', display_name: 'CAD/USD' },
134+
];
135+
const marketsOrder = chartStore.getMarketsOrder(active_symbols);
136+
137+
expect(marketsOrder).toEqual(['AUDUSD', 'CADUSD', 'EURUSD']);
138+
});
139+
140+
it('should get markets order with synthetic index', () => {
141+
const active_symbols = [{ market: 'synthetic_index', display_name: 'Synthetic Index' }];
142+
const marketsOrder = chartStore.getMarketsOrder(active_symbols);
143+
144+
expect(marketsOrder).toEqual(['synthetic_index']);
145+
});
146+
147+
it('should update symbol and save to local storage', () => {
148+
const mockSymbol = 'USDJPY';
149+
chartStore.onSymbolChange(mockSymbol);
150+
151+
expect(chartStore.symbol).toEqual(mockSymbol);
152+
});
153+
154+
it('should call restoreFromStorage ', () => {
155+
LocalStore.set('bot.chart_props', '{none}');
156+
chartStore.restoreFromStorage();
157+
});
158+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { mockStore } from '@deriv/stores';
2+
import { TStores } from '@deriv/stores/types';
3+
import { mock_ws } from 'Utils/mock';
4+
import { mockDBotStore } from 'Stores/useDBotStore';
5+
import ToolbarStore from '../toolbar-store';
6+
7+
jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({}));
8+
9+
const mock_undo = jest.fn();
10+
const mock_cleanup = jest.fn();
11+
const mock_zoom = jest.fn();
12+
13+
window.Blockly = {
14+
derivWorkspace: {
15+
undo: mock_undo,
16+
hasRedoStack: jest.fn(),
17+
hasUndoStack: jest.fn(),
18+
cached_xml: {
19+
main: 'main',
20+
},
21+
getMetrics: jest.fn(() => ({
22+
viewWidth: 100,
23+
viewHeight: 100,
24+
})),
25+
zoom: mock_zoom,
26+
cleanUp: mock_cleanup,
27+
},
28+
utils: {
29+
genUid: jest.fn(),
30+
},
31+
Events: {
32+
setGroup: jest.fn(),
33+
},
34+
svgResize: jest.fn(),
35+
};
36+
37+
describe('ToolbarStore', () => {
38+
const mock_store: TStores = mockStore({});
39+
const mock_DBot_store = mockDBotStore(mock_store, mock_ws);
40+
let toolbarStore: ToolbarStore;
41+
42+
beforeEach(() => {
43+
toolbarStore = new ToolbarStore(mock_DBot_store);
44+
});
45+
46+
it('should initialize ToolbarStore with default values', () => {
47+
expect(toolbarStore.is_animation_info_modal_open).toBe(false);
48+
expect(toolbarStore.is_dialog_open).toBe(false);
49+
expect(toolbarStore.file_name).toBe('Untitled Bot');
50+
expect(toolbarStore.has_undo_stack).toBe(false);
51+
expect(toolbarStore.has_redo_stack).toBe(false);
52+
expect(toolbarStore.is_reset_button_clicked).toBe(false);
53+
});
54+
55+
it('should show dialog on reset button click', () => {
56+
toolbarStore.onResetClick();
57+
expect(toolbarStore.is_dialog_open).toBe(true);
58+
});
59+
60+
it('should hide dialog on close reset dialog click', () => {
61+
toolbarStore.closeResetDialog();
62+
expect(toolbarStore.is_dialog_open).toBe(false);
63+
});
64+
65+
it('should not show dialog onResetOkButtonClick', () => {
66+
toolbarStore.onResetOkButtonClick();
67+
expect(toolbarStore.is_dialog_open).toBe(false);
68+
expect(toolbarStore.is_reset_button_clicked).toBe(false);
69+
});
70+
71+
it('should show dialog onResetOkButtonClick while bot is running', () => {
72+
mock_DBot_store.run_panel.setIsRunning(true);
73+
toolbarStore.onResetOkButtonClick();
74+
expect(toolbarStore.is_reset_button_clicked).toBe(true);
75+
});
76+
77+
it('should call reset default strategy', async () => {
78+
await toolbarStore.resetDefaultStrategy();
79+
expect(window.Blockly.derivWorkspace.strategy_to_load).toBe('main');
80+
});
81+
82+
it('should call onSortClick', () => {
83+
toolbarStore.onSortClick();
84+
expect(mock_cleanup).toHaveBeenCalled();
85+
});
86+
87+
it('should handle undo and redo click correctly', () => {
88+
toolbarStore.onUndoClick(true);
89+
expect(mock_undo).toHaveBeenCalledWith(true);
90+
});
91+
92+
it('should call onZoomInOutClick ', () => {
93+
toolbarStore.onZoomInOutClick(true);
94+
expect(mock_zoom).toHaveBeenCalledWith(50, 50, 1);
95+
});
96+
97+
it('should call onZoomInOutClick ', () => {
98+
toolbarStore.onZoomInOutClick(false);
99+
expect(mock_zoom).toHaveBeenCalledWith(50, 50, -1);
100+
});
101+
});

packages/bot-web-ui/src/stores/chart-store.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { action, computed, makeObservable, observable, reaction } from 'mobx';
2-
// import { tabs_title } from '../constants/bot-contents';
3-
import { ServerTime } from '@deriv/bot-skeleton';
4-
import { LocalStore } from '@deriv/shared';
5-
import RootStore from './root-store';
2+
// eslint-disable-next-line import/no-extraneous-dependencies
63
import {
74
ActiveSymbolsRequest,
85
ServerTimeRequest,
96
TicksHistoryRequest,
107
TicksStreamRequest,
118
TradingTimesRequest,
129
} from '@deriv/api-types';
10+
import { ServerTime } from '@deriv/bot-skeleton';
11+
import { LocalStore } from '@deriv/shared';
12+
import RootStore from './root-store';
1313

14-
const g_subscribers_map: Partial<Record<string, ReturnType<typeof WS.subscribeTicksHistory>>> = {};
14+
export const g_subscribers_map: Partial<Record<string, ReturnType<typeof WS.subscribeTicksHistory>>> = {};
1515
let WS: RootStore['ws'];
1616

1717
export default class ChartStore {

packages/bot-web-ui/src/stores/toolbar-store.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ interface IToolbarStore {
1919
setHasRedoStack: () => void;
2020
}
2121

22-
const Blockly = window.Blockly;
2322
export default class ToolbarStore implements IToolbarStore {
2423
root_store: any;
2524

@@ -79,8 +78,8 @@ export default class ToolbarStore implements IToolbarStore {
7978
};
8079

8180
resetDefaultStrategy = async () => {
82-
const workspace = Blockly.derivWorkspace;
83-
workspace.current_strategy_id = Blockly.utils.genUid();
81+
const workspace = window.Blockly.derivWorkspace;
82+
workspace.current_strategy_id = window.Blockly.utils.genUid();
8483
await load({
8584
block_string: workspace.cached_xml.main,
8685
file_name: config.default_file_name,
@@ -99,31 +98,31 @@ export default class ToolbarStore implements IToolbarStore {
9998
indentWorkspace: { x, y },
10099
},
101100
} = config;
102-
Blockly.derivWorkspace.cleanUp(x, y);
101+
window.Blockly.derivWorkspace.cleanUp(x, y);
103102
};
104103

105104
onUndoClick = (is_redo: boolean): void => {
106-
Blockly.Events.setGroup('undo_clicked');
107-
Blockly.derivWorkspace.undo(is_redo);
108-
Blockly.svgResize(Blockly.derivWorkspace); // Called for CommentDelete event.
105+
window.Blockly.Events.setGroup('undo_clicked');
106+
window.Blockly.derivWorkspace.undo(is_redo);
107+
window.Blockly.svgResize(window.Blockly.derivWorkspace); // Called for CommentDelete event.
109108
this.setHasRedoStack();
110109
this.setHasUndoStack();
111-
Blockly.Events.setGroup(false);
110+
window.Blockly.Events.setGroup(false);
112111
};
113112

114113
onZoomInOutClick = (is_zoom_in: boolean): void => {
115-
const workspace = Blockly.derivWorkspace;
114+
const workspace = window.Blockly.derivWorkspace;
116115
const metrics = workspace.getMetrics();
117116
const addition = is_zoom_in ? 1 : -1;
118117

119118
workspace.zoom(metrics.viewWidth / 2, metrics.viewHeight / 2, addition);
120119
};
121120

122121
setHasUndoStack = (): void => {
123-
this.has_undo_stack = Blockly.derivWorkspace?.hasUndoStack();
122+
this.has_undo_stack = window.Blockly.derivWorkspace?.hasUndoStack();
124123
};
125124

126125
setHasRedoStack = (): void => {
127-
this.has_redo_stack = Blockly.derivWorkspace?.hasRedoStack();
126+
this.has_redo_stack = window.Blockly.derivWorkspace?.hasRedoStack();
128127
};
129128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// to use this localStorage mock, add the following import statement to the test file:
2+
// import 'Utils/mock/mock-local-storage';
3+
4+
let store: Record<string, string> = {};
5+
6+
export const mockLocalStorage = (() => {
7+
return {
8+
getItem(key: string) {
9+
return store[key] || null;
10+
},
11+
setItem(key: string, value: string) {
12+
store[key] = value.toString();
13+
},
14+
clear() {
15+
store = {};
16+
},
17+
removeItem(key: string) {
18+
delete store[key];
19+
},
20+
};
21+
})();

packages/bot-web-ui/src/utils/mock/ws-mock.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ export const mock_ws = {
88
send: jest.fn(),
99
},
1010
contractUpdate: jest.fn(),
11-
subscribeTicksHistory: jest.fn(),
11+
subscribeTicksHistory: jest.fn((req, callback) => {
12+
const subscriber = {
13+
history: {
14+
times: [],
15+
prices: [],
16+
},
17+
msg_type: 'history',
18+
pip_size: 3,
19+
req_id: 31,
20+
unsubscribe: jest.fn(),
21+
};
22+
callback();
23+
return subscriber;
24+
}),
1225
forgetStream: jest.fn(),
1326
activeSymbols: jest.fn(),
1427
send: jest.fn(),

0 commit comments

Comments
 (0)