@oanda/labs-crowd-view-widget 1.0.51 → 1.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +60 -21
  3. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +3 -3
  5. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +208 -42
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  9. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +25 -6
  10. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +12 -10
  12. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  13. package/dist/main/CrowdViewWidget/components/Chart/utils/getChartStyles.js +27 -0
  14. package/dist/main/CrowdViewWidget/components/Chart/utils/getChartStyles.js.map +1 -0
  15. package/dist/main/CrowdViewWidget/components/Chart/utils/getGridLines.js +123 -0
  16. package/dist/main/CrowdViewWidget/components/Chart/utils/getGridLines.js.map +1 -0
  17. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +22 -0
  18. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -1
  19. package/dist/main/CrowdViewWidget/components/Chart/utils/processSentiments.js +28 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/utils/processSentiments.js.map +1 -0
  21. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +1 -1
  22. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  23. package/dist/main/CrowdViewWidget/constants.js +13 -3
  24. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  25. package/dist/main/gql/getSentiments.js +11 -0
  26. package/dist/main/gql/getSentiments.js.map +1 -0
  27. package/dist/main/gql/types/gql.js +2 -1
  28. package/dist/main/gql/types/gql.js.map +1 -1
  29. package/dist/main/gql/types/graphql.js +162 -1
  30. package/dist/main/gql/types/graphql.js.map +1 -1
  31. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +63 -24
  32. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  33. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +3 -3
  34. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  35. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +208 -43
  36. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  37. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  38. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +26 -7
  39. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  40. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +12 -10
  41. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  42. package/dist/module/CrowdViewWidget/components/Chart/utils/getChartStyles.js +20 -0
  43. package/dist/module/CrowdViewWidget/components/Chart/utils/getChartStyles.js.map +1 -0
  44. package/dist/module/CrowdViewWidget/components/Chart/utils/getGridLines.js +116 -0
  45. package/dist/module/CrowdViewWidget/components/Chart/utils/getGridLines.js.map +1 -0
  46. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +2 -0
  47. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -1
  48. package/dist/module/CrowdViewWidget/components/Chart/utils/processSentiments.js +21 -0
  49. package/dist/module/CrowdViewWidget/components/Chart/utils/processSentiments.js.map +1 -0
  50. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +1 -1
  51. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  52. package/dist/module/CrowdViewWidget/constants.js +12 -2
  53. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  54. package/dist/module/gql/getSentiments.js +6 -0
  55. package/dist/module/gql/getSentiments.js.map +1 -0
  56. package/dist/module/gql/types/gql.js +2 -1
  57. package/dist/module/gql/types/gql.js.map +1 -1
  58. package/dist/module/gql/types/graphql.js +161 -0
  59. package/dist/module/gql/types/graphql.js.map +1 -1
  60. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +43 -0
  61. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +2 -2
  62. package/dist/types/CrowdViewWidget/components/Chart/utils/getChartStyles.d.ts +10 -0
  63. package/dist/types/CrowdViewWidget/components/Chart/utils/getGridLines.d.ts +97 -0
  64. package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +2 -0
  65. package/dist/types/CrowdViewWidget/components/Chart/utils/processSentiments.d.ts +3 -0
  66. package/dist/types/CrowdViewWidget/constants.d.ts +11 -1
  67. package/dist/types/gql/getSentiments.d.ts +2 -0
  68. package/dist/types/gql/types/gql.d.ts +9 -0
  69. package/dist/types/gql/types/graphql.d.ts +36 -0
  70. package/package.json +3 -3
  71. package/src/CrowdViewWidget/components/Chart/Chart.tsx +86 -34
  72. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +3 -3
  73. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +242 -72
  74. package/src/CrowdViewWidget/components/Chart/types.ts +55 -0
  75. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +35 -3
  76. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +33 -14
  77. package/src/CrowdViewWidget/components/Chart/utils/getChartStyles.ts +42 -0
  78. package/src/CrowdViewWidget/components/Chart/utils/getGridLines.ts +148 -0
  79. package/src/CrowdViewWidget/components/Chart/utils/index.ts +2 -0
  80. package/src/CrowdViewWidget/components/Chart/utils/processSentiments.ts +42 -0
  81. package/src/CrowdViewWidget/components/Legend/Legend.tsx +1 -1
  82. package/src/CrowdViewWidget/constants.ts +17 -1
  83. package/src/gql/getSentiments.ts +25 -0
  84. package/src/gql/types/gql.ts +8 -0
  85. package/src/gql/types/graphql.ts +161 -0
  86. package/test/components/Chart/utils/chartUtils.test.ts +76 -2
  87. package/test/components/Chart/utils/getChartStyles.test.ts +64 -0
  88. package/test/components/Chart/utils/processSentiments.test.ts +130 -0
@@ -248,6 +248,7 @@ export type Query = {
248
248
  resolveInstrumentsWithFilters?: Maybe<InstrumentTableResult>;
249
249
  sentiment?: Maybe<Array<SentimentInstrument>>;
250
250
  sentimentList?: Maybe<Array<SentimentInstrument>>;
251
+ sentiments: SentimentData;
251
252
  topicalInstruments?: Maybe<Array<TopicalInstrument>>;
252
253
  topicalInstrumentsCharts?: Maybe<Array<TopicalInstrumentChart>>;
253
254
  topicalInstrumentsTotalCount: Scalars['Int']['output'];
@@ -342,6 +343,12 @@ export type QuerySentimentListArgs = {
342
343
  sort?: InputMaybe<Sort>;
343
344
  };
344
345
 
346
+ export type QuerySentimentsArgs = {
347
+ granularity: Granularity;
348
+ instrument: Scalars['String']['input'];
349
+ timeSpan: TimeSpan;
350
+ };
351
+
345
352
  export type QueryTopicalInstrumentsArgs = {
346
353
  assetClass?: InputMaybe<AssetClassName>;
347
354
  count?: InputMaybe<Scalars['Int']['input']>;
@@ -395,6 +402,11 @@ export type Sentiment = {
395
402
  shortPercent: Scalars['Float']['output'];
396
403
  };
397
404
 
405
+ export type SentimentData = {
406
+ __typename?: 'SentimentData';
407
+ sentiments: Array<Maybe<SentimentWithTime>>;
408
+ };
409
+
398
410
  export type SentimentInstrument = {
399
411
  __typename?: 'SentimentInstrument';
400
412
  displayName: Scalars['String']['output'];
@@ -403,6 +415,12 @@ export type SentimentInstrument = {
403
415
  updatedAt: Scalars['String']['output'];
404
416
  };
405
417
 
418
+ export type SentimentWithTime = {
419
+ __typename?: 'SentimentWithTime';
420
+ sentiment: Sentiment;
421
+ time: Scalars['String']['output'];
422
+ };
423
+
406
424
  export enum Sort {
407
425
  Bearish = 'BEARISH',
408
426
  Bullish = 'BULLISH',
@@ -572,6 +590,28 @@ export type GetPriceCandlesQuery = {
572
590
  };
573
591
  };
574
592
 
593
+ export type GetSentimentsQueryVariables = Exact<{
594
+ instrument: Scalars['String']['input'];
595
+ granularity: Granularity;
596
+ timeSpan: TimeSpan;
597
+ }>;
598
+
599
+ export type GetSentimentsQuery = {
600
+ __typename?: 'Query';
601
+ sentiments: {
602
+ __typename?: 'SentimentData';
603
+ sentiments: Array<{
604
+ __typename?: 'SentimentWithTime';
605
+ time: string;
606
+ sentiment: {
607
+ __typename?: 'Sentiment';
608
+ longPercent: number;
609
+ shortPercent: number;
610
+ };
611
+ } | null>;
612
+ };
613
+ };
614
+
575
615
  export const GetOrderPositionBooksDocument = {
576
616
  kind: 'Document',
577
617
  definitions: [
@@ -895,3 +935,124 @@ export const GetPriceCandlesDocument = {
895
935
  GetPriceCandlesQuery,
896
936
  GetPriceCandlesQueryVariables
897
937
  >;
938
+ export const GetSentimentsDocument = {
939
+ kind: 'Document',
940
+ definitions: [
941
+ {
942
+ kind: 'OperationDefinition',
943
+ operation: 'query',
944
+ name: { kind: 'Name', value: 'GetSentiments' },
945
+ variableDefinitions: [
946
+ {
947
+ kind: 'VariableDefinition',
948
+ variable: {
949
+ kind: 'Variable',
950
+ name: { kind: 'Name', value: 'instrument' },
951
+ },
952
+ type: {
953
+ kind: 'NonNullType',
954
+ type: {
955
+ kind: 'NamedType',
956
+ name: { kind: 'Name', value: 'String' },
957
+ },
958
+ },
959
+ },
960
+ {
961
+ kind: 'VariableDefinition',
962
+ variable: {
963
+ kind: 'Variable',
964
+ name: { kind: 'Name', value: 'granularity' },
965
+ },
966
+ type: {
967
+ kind: 'NonNullType',
968
+ type: {
969
+ kind: 'NamedType',
970
+ name: { kind: 'Name', value: 'Granularity' },
971
+ },
972
+ },
973
+ },
974
+ {
975
+ kind: 'VariableDefinition',
976
+ variable: {
977
+ kind: 'Variable',
978
+ name: { kind: 'Name', value: 'timeSpan' },
979
+ },
980
+ type: {
981
+ kind: 'NonNullType',
982
+ type: {
983
+ kind: 'NamedType',
984
+ name: { kind: 'Name', value: 'TimeSpan' },
985
+ },
986
+ },
987
+ },
988
+ ],
989
+ selectionSet: {
990
+ kind: 'SelectionSet',
991
+ selections: [
992
+ {
993
+ kind: 'Field',
994
+ name: { kind: 'Name', value: 'sentiments' },
995
+ arguments: [
996
+ {
997
+ kind: 'Argument',
998
+ name: { kind: 'Name', value: 'instrument' },
999
+ value: {
1000
+ kind: 'Variable',
1001
+ name: { kind: 'Name', value: 'instrument' },
1002
+ },
1003
+ },
1004
+ {
1005
+ kind: 'Argument',
1006
+ name: { kind: 'Name', value: 'granularity' },
1007
+ value: {
1008
+ kind: 'Variable',
1009
+ name: { kind: 'Name', value: 'granularity' },
1010
+ },
1011
+ },
1012
+ {
1013
+ kind: 'Argument',
1014
+ name: { kind: 'Name', value: 'timeSpan' },
1015
+ value: {
1016
+ kind: 'Variable',
1017
+ name: { kind: 'Name', value: 'timeSpan' },
1018
+ },
1019
+ },
1020
+ ],
1021
+ selectionSet: {
1022
+ kind: 'SelectionSet',
1023
+ selections: [
1024
+ {
1025
+ kind: 'Field',
1026
+ name: { kind: 'Name', value: 'sentiments' },
1027
+ selectionSet: {
1028
+ kind: 'SelectionSet',
1029
+ selections: [
1030
+ {
1031
+ kind: 'Field',
1032
+ name: { kind: 'Name', value: 'sentiment' },
1033
+ selectionSet: {
1034
+ kind: 'SelectionSet',
1035
+ selections: [
1036
+ {
1037
+ kind: 'Field',
1038
+ name: { kind: 'Name', value: 'longPercent' },
1039
+ },
1040
+ {
1041
+ kind: 'Field',
1042
+ name: { kind: 'Name', value: 'shortPercent' },
1043
+ },
1044
+ ],
1045
+ },
1046
+ },
1047
+ { kind: 'Field', name: { kind: 'Name', value: 'time' } },
1048
+ ],
1049
+ },
1050
+ },
1051
+ ],
1052
+ },
1053
+ },
1054
+ ],
1055
+ },
1056
+ },
1057
+ ],
1058
+ } as unknown as DocumentNode<GetSentimentsQuery, GetSentimentsQueryVariables>;
@@ -1,3 +1,4 @@
1
+ import type { TooltipParam } from '../../../../src/CrowdViewWidget/components/Chart/types';
1
2
  import {
2
3
  formatXAxisLabel,
3
4
  getLabelData,
@@ -137,12 +138,16 @@ describe('chartUtils', () => {
137
138
  const labelCallback = (k: string) => k;
138
139
 
139
140
  it('renders candle and book details when available', () => {
140
- const params = [
141
+ const params: TooltipParam[] = [
141
142
  {
143
+ seriesId: 'candlestick' as const,
142
144
  axisValue: '2025-03-15T10:30:00Z',
143
145
  value: [0, 1.11111, 1.22222, 1.00001, 1.33333],
144
146
  },
145
- { value: ['2025-03-15T10:30:00Z', 1.33333, 0] },
147
+ {
148
+ seriesId: 'heatmap' as const,
149
+ value: ['2025-03-15T10:30:00Z', 1.33333, 0],
150
+ },
146
151
  ];
147
152
 
148
153
  const buckets = [
@@ -161,6 +166,7 @@ describe('chartUtils', () => {
161
166
  bookType: BookType.Order,
162
167
  labelCallback,
163
168
  });
169
+ expect(html).toBeDefined();
164
170
  expect(html).toContain('candle');
165
171
  expect(html).toContain('open_price');
166
172
  expect(html).toContain('close_price');
@@ -171,5 +177,73 @@ describe('chartUtils', () => {
171
177
  // Selected price 1.3306 falls into second bucket 1.3305 - 1.3310 which has negative sentiment
172
178
  expect(html).toContain('sell_overbalance');
173
179
  });
180
+
181
+ it('renders sentiment details when available', () => {
182
+ const params: TooltipParam[] = [
183
+ {
184
+ seriesId: 'candlestick' as const,
185
+ axisValue: '2025-03-15T10:30:00Z',
186
+ value: [0, 1.11111, 1.22222, 1.00001, 1.33333],
187
+ },
188
+ {
189
+ seriesId: 'sentiment' as const,
190
+ value: ['2025-03-15T10:30:00Z', 30.5, 69.5],
191
+ },
192
+ ];
193
+
194
+ const buckets: never[] = [];
195
+
196
+ const html = getTooltipFormatter({
197
+ params,
198
+ buckets,
199
+ bucketWidth: 0.0005,
200
+ selectedPrice: 1.3306,
201
+ precision: 5,
202
+ bookType: BookType.Order,
203
+ labelCallback,
204
+ });
205
+ expect(html).toBeDefined();
206
+ expect(html).toContain('candle');
207
+ expect(html).toContain('sentiment');
208
+ expect(html).toContain('long');
209
+ expect(html).toContain('short');
210
+ expect(html).toContain('30.50');
211
+ expect(html).toContain('69.50');
212
+ });
213
+
214
+ it('returns undefined when no candlestick param is provided', () => {
215
+ const params: TooltipParam[] = [
216
+ {
217
+ seriesId: 'heatmap' as const,
218
+ value: ['2025-03-15T10:30:00Z', 1.33333, 0],
219
+ },
220
+ ];
221
+
222
+ const buckets: never[] = [];
223
+
224
+ const html = getTooltipFormatter({
225
+ params,
226
+ buckets,
227
+ bucketWidth: 0.0005,
228
+ selectedPrice: 1.3306,
229
+ precision: 5,
230
+ bookType: BookType.Order,
231
+ labelCallback,
232
+ });
233
+ expect(html).toBeUndefined();
234
+ });
235
+
236
+ it('returns undefined when params is empty', () => {
237
+ const html = getTooltipFormatter({
238
+ params: [],
239
+ buckets: [],
240
+ bucketWidth: 0.0005,
241
+ selectedPrice: 1.3306,
242
+ precision: 5,
243
+ bookType: BookType.Order,
244
+ labelCallback,
245
+ });
246
+ expect(html).toBeUndefined();
247
+ });
174
248
  });
175
249
  });
@@ -0,0 +1,64 @@
1
+ import { colorPalette } from '@oanda/labs-widget-common';
2
+
3
+ import { getChartStyles } from '../../../../src/CrowdViewWidget/components/Chart/utils/getChartStyles';
4
+
5
+ describe('getChartStyles', () => {
6
+ it('returns correct styles for dark mode', () => {
7
+ const styles = getChartStyles(true);
8
+
9
+ expect(styles.sentimentLongColor).toBe(colorPalette.darkBlue90);
10
+ expect(styles.sentimentShortColor).toBe(colorPalette.darkYellow90);
11
+ expect(styles.candleLongColor).toBe(colorPalette.bottleGreenDark);
12
+ expect(styles.candleShortColor).toBe(colorPalette.orange);
13
+ expect(styles.sentimentAreaOpacity).toBe(0.5);
14
+ expect(styles.tooltipLinesColor).toBe(colorPalette.orange);
15
+ expect(styles.sentimentLabelColor).toBe(colorPalette.white);
16
+ });
17
+
18
+ it('returns correct styles for light mode', () => {
19
+ const styles = getChartStyles(false);
20
+
21
+ expect(styles.sentimentLongColor).toBe(colorPalette.lightBlue90);
22
+ expect(styles.sentimentShortColor).toBe(colorPalette.lightYellow90);
23
+ expect(styles.candleLongColor).toBe(colorPalette.bottleGreenLight);
24
+ expect(styles.candleShortColor).toBe(colorPalette.raspberryLight);
25
+ expect(styles.sentimentAreaOpacity).toBe(0.2);
26
+ expect(styles.tooltipLinesColor).toBe(colorPalette.bottleGreenLight);
27
+ expect(styles.sentimentLabelColor).toBe(colorPalette.black);
28
+ });
29
+
30
+ it('returns all required style properties', () => {
31
+ const darkStyles = getChartStyles(true);
32
+ const lightStyles = getChartStyles(false);
33
+
34
+ const requiredProperties = [
35
+ 'sentimentLongColor',
36
+ 'sentimentShortColor',
37
+ 'candleLongColor',
38
+ 'candleShortColor',
39
+ 'sentimentAreaOpacity',
40
+ 'tooltipLinesColor',
41
+ 'sentimentLabelColor',
42
+ ];
43
+
44
+ requiredProperties.forEach((prop) => {
45
+ expect(darkStyles).toHaveProperty(prop);
46
+ expect(lightStyles).toHaveProperty(prop);
47
+ expect(typeof darkStyles[prop as keyof typeof darkStyles]).not.toBe(
48
+ 'undefined'
49
+ );
50
+ expect(typeof lightStyles[prop as keyof typeof lightStyles]).not.toBe(
51
+ 'undefined'
52
+ );
53
+ });
54
+ });
55
+
56
+ it('returns different opacity values for dark and light modes', () => {
57
+ const darkStyles = getChartStyles(true);
58
+ const lightStyles = getChartStyles(false);
59
+
60
+ expect(darkStyles.sentimentAreaOpacity).toBeGreaterThan(
61
+ lightStyles.sentimentAreaOpacity
62
+ );
63
+ });
64
+ });
@@ -0,0 +1,130 @@
1
+ import { processSentiments } from '../../../../src/CrowdViewWidget/components/Chart/utils/processSentiments';
2
+ import type { GetSentimentsQuery } from '../../../../src/gql/types/graphql';
3
+
4
+ describe('processSentiments', () => {
5
+ it('returns empty array when sentimentsData is undefined', () => {
6
+ const result = processSentiments(undefined, ['2025-03-15T10:30:00Z']);
7
+ expect(result).toEqual([]);
8
+ });
9
+
10
+ it('returns empty array when sentiments array is empty', () => {
11
+ const sentimentsData: GetSentimentsQuery = {
12
+ sentiments: {
13
+ sentiments: [],
14
+ },
15
+ };
16
+ const result = processSentiments(sentimentsData, ['2025-03-15T10:30:00Z']);
17
+ expect(result).toEqual([]);
18
+ });
19
+
20
+ it('returns empty array when sentiments is null', () => {
21
+ const sentimentsData: GetSentimentsQuery = {
22
+ sentiments: {
23
+ sentiments: null as never,
24
+ },
25
+ };
26
+ const result = processSentiments(sentimentsData, ['2025-03-15T10:30:00Z']);
27
+ expect(result).toEqual([]);
28
+ });
29
+
30
+ it('processes sentiments and matches with xAxisData', () => {
31
+ const xAxisData = [
32
+ '2025-03-15T10:30:00Z',
33
+ '2025-03-15T11:30:00Z',
34
+ '2025-03-15T12:30:00Z',
35
+ ];
36
+ const sentimentsData: GetSentimentsQuery = {
37
+ sentiments: {
38
+ sentiments: [
39
+ {
40
+ time: '2025-03-15T10:30:00Z',
41
+ sentiment: {
42
+ shortPercent: 30.5,
43
+ longPercent: 69.5,
44
+ },
45
+ },
46
+ {
47
+ time: '2025-03-15T11:30:00Z',
48
+ sentiment: {
49
+ shortPercent: 40.2,
50
+ longPercent: 59.8,
51
+ },
52
+ },
53
+ ],
54
+ },
55
+ };
56
+
57
+ const result = processSentiments(sentimentsData, xAxisData);
58
+ expect(result).toEqual([
59
+ ['2025-03-15T10:30:00Z', 30.5, 69.5],
60
+ ['2025-03-15T11:30:00Z', 40.2, 59.8],
61
+ ]);
62
+ });
63
+
64
+ it('filters out sentiments that do not match xAxisData', () => {
65
+ const xAxisData = ['2025-03-15T10:30:00Z', '2025-03-15T11:30:00Z'];
66
+ const sentimentsData: GetSentimentsQuery = {
67
+ sentiments: {
68
+ sentiments: [
69
+ {
70
+ time: '2025-03-15T10:30:00Z',
71
+ sentiment: {
72
+ shortPercent: 30.5,
73
+ longPercent: 69.5,
74
+ },
75
+ },
76
+ {
77
+ time: '2025-03-15T12:30:00Z', // Not in xAxisData
78
+ sentiment: {
79
+ shortPercent: 50.0,
80
+ longPercent: 50.0,
81
+ },
82
+ },
83
+ ],
84
+ },
85
+ };
86
+
87
+ const result = processSentiments(sentimentsData, xAxisData);
88
+ expect(result).toEqual([['2025-03-15T10:30:00Z', 30.5, 69.5]]);
89
+ });
90
+
91
+ it('filters out sentiments with missing values', () => {
92
+ const xAxisData = ['2025-03-15T10:30:00Z'];
93
+ const sentimentsData: GetSentimentsQuery = {
94
+ sentiments: {
95
+ sentiments: [
96
+ {
97
+ time: '2025-03-15T10:30:00Z',
98
+ sentiment: {
99
+ shortPercent: null as never,
100
+ longPercent: null as never,
101
+ },
102
+ },
103
+ ],
104
+ },
105
+ };
106
+
107
+ const result = processSentiments(sentimentsData, xAxisData);
108
+ expect(result).toEqual([]);
109
+ });
110
+
111
+ it('returns empty array when no sentiments match xAxisData', () => {
112
+ const xAxisData = ['2025-03-15T10:30:00Z'];
113
+ const sentimentsData: GetSentimentsQuery = {
114
+ sentiments: {
115
+ sentiments: [
116
+ {
117
+ time: '2025-03-15T11:30:00Z',
118
+ sentiment: {
119
+ shortPercent: 30.5,
120
+ longPercent: 69.5,
121
+ },
122
+ },
123
+ ],
124
+ },
125
+ };
126
+
127
+ const result = processSentiments(sentimentsData, xAxisData);
128
+ expect(result).toEqual([]);
129
+ });
130
+ });