@oanda/labs-crowd-view-widget 1.0.44 → 1.0.46

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 (131) hide show
  1. package/CHANGELOG.md +372 -0
  2. package/dist/main/CrowdViewWidget/Main.js +20 -7
  3. package/dist/main/CrowdViewWidget/Main.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +6 -10
  5. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +46 -20
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/index.js +4 -4
  9. package/dist/main/CrowdViewWidget/components/Chart/index.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +18 -88
  12. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  13. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +37 -0
  14. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  15. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +54 -2
  16. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  17. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +14 -0
  18. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  19. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +83 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  21. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js +29 -0
  22. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  23. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +23 -0
  24. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  25. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +43 -0
  26. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  27. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js +23 -0
  28. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  29. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +5 -3
  30. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  31. package/dist/main/CrowdViewWidget/constants.js +105 -5
  32. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  33. package/dist/main/CrowdViewWidget/selectConfig.js +18 -60
  34. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  35. package/dist/main/CrowdViewWidget/types.js +20 -0
  36. package/dist/main/CrowdViewWidget/types.js.map +1 -1
  37. package/dist/main/translations/sources/en.json +29 -0
  38. package/dist/module/CrowdViewWidget/Main.js +21 -8
  39. package/dist/module/CrowdViewWidget/Main.js.map +1 -1
  40. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +7 -11
  41. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  42. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +47 -21
  43. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  44. package/dist/module/CrowdViewWidget/components/Chart/index.js +1 -1
  45. package/dist/module/CrowdViewWidget/components/Chart/index.js.map +1 -1
  46. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  47. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +14 -84
  48. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  49. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +29 -0
  50. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  51. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +52 -2
  52. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  53. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +7 -0
  54. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  55. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +8 -0
  56. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  57. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js +22 -0
  58. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  59. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +16 -0
  60. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  61. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +36 -0
  62. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  63. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js +16 -0
  64. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  65. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +5 -3
  66. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  67. package/dist/module/CrowdViewWidget/constants.js +104 -4
  68. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  69. package/dist/module/CrowdViewWidget/selectConfig.js +3 -45
  70. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  71. package/dist/module/CrowdViewWidget/types.js +19 -1
  72. package/dist/module/CrowdViewWidget/types.js.map +1 -1
  73. package/dist/module/translations/sources/en.json +29 -0
  74. package/dist/types/CrowdViewWidget/components/Chart/index.d.ts +1 -1
  75. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +12 -4
  76. package/dist/types/CrowdViewWidget/components/Chart/utils/aggregateBuckets.d.ts +2 -0
  77. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +12 -2
  78. package/dist/types/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.d.ts +3 -0
  79. package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +7 -0
  80. package/dist/types/CrowdViewWidget/components/Chart/utils/processBuckets.d.ts +3 -0
  81. package/dist/types/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.d.ts +8 -0
  82. package/dist/types/CrowdViewWidget/components/Chart/utils/processPriceCandles.d.ts +27 -0
  83. package/dist/types/CrowdViewWidget/components/Chart/utils/validateData.d.ts +2 -0
  84. package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +3 -1
  85. package/dist/types/CrowdViewWidget/constants.d.ts +11 -3
  86. package/dist/types/CrowdViewWidget/selectConfig.d.ts +2 -2
  87. package/dist/types/CrowdViewWidget/types.d.ts +18 -1
  88. package/dist/types/CrowdViewWidget/utils/instrumentUtils.d.ts +1 -4
  89. package/lokalise.config.json +1 -1
  90. package/package.json +4 -3
  91. package/src/CrowdViewWidget/Main.tsx +25 -10
  92. package/src/CrowdViewWidget/components/Chart/Chart.tsx +6 -12
  93. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +70 -36
  94. package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
  95. package/src/CrowdViewWidget/components/Chart/types.ts +16 -4
  96. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +41 -140
  97. package/src/CrowdViewWidget/components/Chart/utils/aggregateBuckets.ts +44 -0
  98. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +96 -3
  99. package/src/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.ts +13 -0
  100. package/src/CrowdViewWidget/components/Chart/utils/index.ts +7 -0
  101. package/src/CrowdViewWidget/components/Chart/utils/processBuckets.ts +43 -0
  102. package/src/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.ts +30 -0
  103. package/src/CrowdViewWidget/components/Chart/utils/processPriceCandles.ts +53 -0
  104. package/src/CrowdViewWidget/components/Chart/utils/validateData.ts +27 -0
  105. package/src/CrowdViewWidget/components/Legend/Legend.tsx +13 -2
  106. package/src/CrowdViewWidget/constants.ts +113 -3
  107. package/src/CrowdViewWidget/selectConfig.ts +5 -60
  108. package/src/CrowdViewWidget/types.ts +18 -1
  109. package/src/translations/sources/en.json +29 -0
  110. package/test/Main.test.tsx +73 -27
  111. package/test/components/Chart/utils/chartUtils.test.ts +158 -0
  112. package/test/components/Legend.test.tsx +6 -1
  113. package/test/utils/aggregateBuckets.test.ts +82 -0
  114. package/test/utils/getTargetBucketWidth.test.ts +37 -0
  115. package/test/utils/instrumentUtils.test.ts +13 -7
  116. package/test/utils/processBuckets.test.ts +153 -0
  117. package/test/utils/processOrderPositionBooks.test.ts +127 -0
  118. package/test/utils/processPriceCandles.test.ts +245 -0
  119. package/test/utils/validateData.test.ts +201 -0
  120. package/dist/main/CrowdViewWidget/types/index.js +0 -17
  121. package/dist/main/CrowdViewWidget/types/index.js.map +0 -1
  122. package/dist/main/CrowdViewWidget/types/instruments.js +0 -45
  123. package/dist/main/CrowdViewWidget/types/instruments.js.map +0 -1
  124. package/dist/module/CrowdViewWidget/types/index.js +0 -2
  125. package/dist/module/CrowdViewWidget/types/index.js.map +0 -1
  126. package/dist/module/CrowdViewWidget/types/instruments.js +0 -39
  127. package/dist/module/CrowdViewWidget/types/instruments.js.map +0 -1
  128. package/dist/types/CrowdViewWidget/types/index.d.ts +0 -1
  129. package/dist/types/CrowdViewWidget/types/instruments.d.ts +0 -36
  130. package/src/CrowdViewWidget/types/index.ts +0 -1
  131. package/src/CrowdViewWidget/types/instruments.ts +0 -37
@@ -14,11 +14,13 @@ describe('instrumentUtils', () => {
14
14
  expect(Array.isArray(result)).toBe(true);
15
15
  expect(result.length).toBeGreaterThan(0);
16
16
 
17
- // Check that the first instrument has the expected OC format
17
+ // Check that the first instrument has the expected structure
18
18
  const firstInstrument = result[0];
19
19
  expect(firstInstrument).toHaveProperty('id');
20
20
  expect(firstInstrument).toHaveProperty('label');
21
- expect(firstInstrument.id).toMatch(/^[A-Z]{3}_[A-Z]{3}$/); // OC format: EUR_USD
21
+ // Instrument IDs are now enum values like 'EURAUD', not 'EUR_AUD'
22
+ expect(typeof firstInstrument.id).toBe('string');
23
+ expect(firstInstrument.id.length).toBeGreaterThan(0);
22
24
  });
23
25
 
24
26
  it('should return OAP instrument config for OAP division', () => {
@@ -28,11 +30,13 @@ describe('instrumentUtils', () => {
28
30
  expect(Array.isArray(result)).toBe(true);
29
31
  expect(result.length).toBeGreaterThan(0);
30
32
 
31
- // Check that the first instrument has the expected OAP format
33
+ // Check that the first instrument has the expected structure
32
34
  const firstInstrument = result[0];
33
35
  expect(firstInstrument).toHaveProperty('id');
34
36
  expect(firstInstrument).toHaveProperty('label');
35
- expect(firstInstrument.id).toMatch(/^[A-Z]{6}$/); // OAP format: EURUSD
37
+ // Instrument IDs are now enum values like 'EURAUD', not 'EURUSD'
38
+ expect(typeof firstInstrument.id).toBe('string');
39
+ expect(firstInstrument.id.length).toBeGreaterThan(0);
36
40
  });
37
41
 
38
42
  it('should return different configs for different divisions', () => {
@@ -41,12 +45,14 @@ describe('instrumentUtils', () => {
41
45
 
42
46
  expect(ocConfig).not.toEqual(oapConfig);
43
47
 
44
- // Check that the instrument IDs have different formats
48
+ // Both configs should have valid instrument IDs
45
49
  const ocFirstId = ocConfig[0].id;
46
50
  const oapFirstId = oapConfig[0].id;
47
51
 
48
- expect(ocFirstId).toMatch(/^[A-Z]{3}_[A-Z]{3}$/);
49
- expect(oapFirstId).toMatch(/^[A-Z]{6}$/);
52
+ expect(typeof ocFirstId).toBe('string');
53
+ expect(typeof oapFirstId).toBe('string');
54
+ // OAP config includes XAU_USD and XAG_USD which OC doesn't have
55
+ expect(oapConfig.length).toBeGreaterThan(ocConfig.length);
50
56
  });
51
57
  });
52
58
  });
@@ -0,0 +1,153 @@
1
+ import { processBuckets } from '../../src/CrowdViewWidget/components';
2
+ import type { GetOrderPositionBooksQuery } from '../../src/gql/types/graphql';
3
+
4
+ describe('processBuckets', () => {
5
+ const DEFAULT_BUCKET_WIDTH = 0.0005;
6
+ const OPTIMIZED_BUCKET_WIDTH = 0.002; // DEFAULT_BUCKET_WIDTH * 4
7
+
8
+ const createMockOrderPositionBook = (
9
+ time: string,
10
+ buckets: Array<{ price: number; sentiment: number | null } | null>,
11
+ bucketWidth: number = DEFAULT_BUCKET_WIDTH
12
+ ) => ({
13
+ bucketWidth,
14
+ price: 1.0,
15
+ time,
16
+ buckets,
17
+ });
18
+
19
+ it('should return empty array when orderPositionData is undefined', () => {
20
+ expect(processBuckets(undefined, DEFAULT_BUCKET_WIDTH)).toEqual([]);
21
+ });
22
+
23
+ it('should return empty array when orderPositionBooks is empty', () => {
24
+ const mockData: GetOrderPositionBooksQuery = {
25
+ orderPositionBooks: [],
26
+ };
27
+ expect(processBuckets(mockData, DEFAULT_BUCKET_WIDTH)).toEqual([]);
28
+ });
29
+
30
+ it('should filter out null books', () => {
31
+ const mockData: GetOrderPositionBooksQuery = {
32
+ orderPositionBooks: [null, createMockOrderPositionBook('2025-01-01', [])],
33
+ };
34
+ expect(processBuckets(mockData, DEFAULT_BUCKET_WIDTH)).toEqual([]);
35
+ });
36
+
37
+ it('should filter out books with no buckets', () => {
38
+ const mockData: GetOrderPositionBooksQuery = {
39
+ orderPositionBooks: [
40
+ createMockOrderPositionBook('2025-01-01', []),
41
+ createMockOrderPositionBook('2025-01-02', [
42
+ { price: 1.0, sentiment: 0.2 },
43
+ ]),
44
+ ],
45
+ };
46
+ const result = processBuckets(mockData, DEFAULT_BUCKET_WIDTH);
47
+ expect(result).toHaveLength(1);
48
+ expect(result[0]).toEqual([{ price: 1.0, sentiment: 0.2 }]);
49
+ });
50
+
51
+ it('should filter out null buckets and buckets with missing price or sentiment', () => {
52
+ const mockData: GetOrderPositionBooksQuery = {
53
+ orderPositionBooks: [
54
+ createMockOrderPositionBook('2025-01-01', [
55
+ null,
56
+ { price: 1.0, sentiment: null },
57
+ { price: undefined as any, sentiment: 0.2 },
58
+ { price: 1.1, sentiment: 0.2 },
59
+ ]),
60
+ ],
61
+ };
62
+ const result = processBuckets(mockData, DEFAULT_BUCKET_WIDTH);
63
+ expect(result).toHaveLength(1);
64
+ expect(result[0]).toEqual([{ price: 1.1, sentiment: 0.2 }]);
65
+ });
66
+
67
+ it('should filter out buckets below sentiment threshold', () => {
68
+ const mockData: GetOrderPositionBooksQuery = {
69
+ orderPositionBooks: [
70
+ createMockOrderPositionBook('2025-01-01', [
71
+ { price: 1.0, sentiment: 0.1 }, // below threshold
72
+ { price: 1.1, sentiment: 0.15 }, // at threshold
73
+ { price: 1.2, sentiment: 0.2 }, // above threshold
74
+ { price: 1.3, sentiment: -0.1 }, // below threshold (absolute)
75
+ { price: 1.4, sentiment: -0.15 }, // at threshold (absolute)
76
+ { price: 1.5, sentiment: -0.2 }, // above threshold (absolute)
77
+ ]),
78
+ ],
79
+ };
80
+ const result = processBuckets(mockData, DEFAULT_BUCKET_WIDTH);
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0]).toEqual([
83
+ { price: 1.1, sentiment: 0.15 },
84
+ { price: 1.2, sentiment: 0.2 },
85
+ { price: 1.4, sentiment: -0.15 },
86
+ { price: 1.5, sentiment: -0.2 },
87
+ ]);
88
+ });
89
+
90
+ it('should aggregate buckets when using optimized width', () => {
91
+ const mockData: GetOrderPositionBooksQuery = {
92
+ orderPositionBooks: [
93
+ createMockOrderPositionBook(
94
+ '2025-01-01',
95
+ [
96
+ { price: 1.0, sentiment: 0.2 },
97
+ { price: 1.0003, sentiment: 0.3 },
98
+ { price: 1.0007, sentiment: 0.1 },
99
+ { price: 1.0012, sentiment: 0.25 },
100
+ ],
101
+ DEFAULT_BUCKET_WIDTH
102
+ ),
103
+ ],
104
+ };
105
+ const result = processBuckets(mockData, OPTIMIZED_BUCKET_WIDTH);
106
+ expect(result).toHaveLength(1);
107
+ // Buckets should be aggregated: 1.0 + 1.0003 + 1.0007 = 1.0 bucket (0.6 sentiment)
108
+ // 1.0012 = 1.001 bucket (0.25 sentiment)
109
+ expect(result[0].length).toBeGreaterThan(0);
110
+ const aggregatedSentiments = result[0].reduce(
111
+ (acc, bucket) => acc + bucket.sentiment,
112
+ 0
113
+ );
114
+ expect(aggregatedSentiments).toBeCloseTo(0.85, 2);
115
+ });
116
+
117
+ it('should not aggregate buckets when using default width', () => {
118
+ const mockData: GetOrderPositionBooksQuery = {
119
+ orderPositionBooks: [
120
+ createMockOrderPositionBook('2025-01-01', [
121
+ { price: 1.0, sentiment: 0.2 },
122
+ { price: 1.0003, sentiment: 0.3 },
123
+ { price: 1.0012, sentiment: 0.25 },
124
+ ]),
125
+ ],
126
+ };
127
+ const result = processBuckets(mockData, DEFAULT_BUCKET_WIDTH);
128
+ expect(result).toHaveLength(1);
129
+ // Should have same number of buckets as input (assuming all pass threshold)
130
+ expect(result[0].length).toBeGreaterThanOrEqual(3);
131
+ });
132
+
133
+ it('should process multiple books correctly', () => {
134
+ const mockData: GetOrderPositionBooksQuery = {
135
+ orderPositionBooks: [
136
+ createMockOrderPositionBook('2025-01-01', [
137
+ { price: 1.0, sentiment: 0.2 },
138
+ ]),
139
+ createMockOrderPositionBook('2025-01-02', [
140
+ { price: 1.1, sentiment: 0.3 },
141
+ { price: 1.2, sentiment: 0.4 },
142
+ ]),
143
+ ],
144
+ };
145
+ const result = processBuckets(mockData, DEFAULT_BUCKET_WIDTH);
146
+ expect(result).toHaveLength(2);
147
+ expect(result[0]).toEqual([{ price: 1.0, sentiment: 0.2 }]);
148
+ expect(result[1]).toEqual([
149
+ { price: 1.1, sentiment: 0.3 },
150
+ { price: 1.2, sentiment: 0.4 },
151
+ ]);
152
+ });
153
+ });
@@ -0,0 +1,127 @@
1
+ import { processOrderPositionBooks } from '../../src/CrowdViewWidget/components';
2
+ import type { GetOrderPositionBooksQuery } from '../../src/gql/types/graphql';
3
+
4
+ describe('processOrderPositionBooks', () => {
5
+ const createMockOrderPositionBook = (time: string) => ({
6
+ bucketWidth: 0.0005,
7
+ price: 1.0,
8
+ time,
9
+ buckets: [{ price: 1.0, sentiment: 0.2 }],
10
+ });
11
+
12
+ it('should return empty array when orderPositionData is undefined', () => {
13
+ const candleMap = new Map();
14
+ expect(processOrderPositionBooks(undefined, candleMap)).toEqual([]);
15
+ });
16
+
17
+ it('should return empty array when orderPositionBooks is empty', () => {
18
+ const mockData: GetOrderPositionBooksQuery = {
19
+ orderPositionBooks: [],
20
+ };
21
+ const candleMap = new Map();
22
+ expect(processOrderPositionBooks(mockData, candleMap)).toEqual([]);
23
+ });
24
+
25
+ it('should filter out null books', () => {
26
+ const mockData: GetOrderPositionBooksQuery = {
27
+ orderPositionBooks: [null, createMockOrderPositionBook('2025-01-01')],
28
+ };
29
+ const candleMap = new Map();
30
+ const result = processOrderPositionBooks(mockData, candleMap);
31
+ expect(result).toHaveLength(1);
32
+ expect(result[0]).toEqual(['2025-01-01', null, 0]);
33
+ });
34
+
35
+ it('should filter out books with no buckets', () => {
36
+ const mockData: GetOrderPositionBooksQuery = {
37
+ orderPositionBooks: [
38
+ {
39
+ bucketWidth: 0.0005,
40
+ price: 1.0,
41
+ time: '2025-01-01',
42
+ buckets: [],
43
+ },
44
+ createMockOrderPositionBook('2025-01-02'),
45
+ ],
46
+ };
47
+ const candleMap = new Map();
48
+ const result = processOrderPositionBooks(mockData, candleMap);
49
+ expect(result).toHaveLength(1);
50
+ expect(result[0]).toEqual(['2025-01-02', null, 0]);
51
+ });
52
+
53
+ it('should use candle high price when available in map', () => {
54
+ const mockData: GetOrderPositionBooksQuery = {
55
+ orderPositionBooks: [
56
+ createMockOrderPositionBook('2025-01-01'),
57
+ createMockOrderPositionBook('2025-01-02'),
58
+ ],
59
+ };
60
+ const candleMap = new Map([
61
+ ['2025-01-01', { high: 1.1234 }],
62
+ ['2025-01-02', { high: 1.5678 }],
63
+ ]);
64
+ const result = processOrderPositionBooks(mockData, candleMap);
65
+ expect(result).toHaveLength(2);
66
+ expect(result[0]).toEqual(['2025-01-01', 1.1234, 0]);
67
+ expect(result[1]).toEqual(['2025-01-02', 1.5678, 1]);
68
+ });
69
+
70
+ it('should use null when candle is not in map', () => {
71
+ const mockData: GetOrderPositionBooksQuery = {
72
+ orderPositionBooks: [createMockOrderPositionBook('2025-01-01')],
73
+ };
74
+ const candleMap = new Map();
75
+ const result = processOrderPositionBooks(mockData, candleMap);
76
+ expect(result).toHaveLength(1);
77
+ expect(result[0]).toEqual(['2025-01-01', null, 0]);
78
+ });
79
+
80
+ it('should use null when candle high is undefined', () => {
81
+ const mockData: GetOrderPositionBooksQuery = {
82
+ orderPositionBooks: [createMockOrderPositionBook('2025-01-01')],
83
+ };
84
+ const candleMap = new Map([
85
+ ['2025-01-01', { low: 1.0 }], // no high property
86
+ ]);
87
+ const result = processOrderPositionBooks(mockData, candleMap);
88
+ expect(result).toHaveLength(1);
89
+ expect(result[0]).toEqual(['2025-01-01', null, 0]);
90
+ });
91
+
92
+ it('should correctly assign index to each book', () => {
93
+ const mockData: GetOrderPositionBooksQuery = {
94
+ orderPositionBooks: [
95
+ createMockOrderPositionBook('2025-01-01'),
96
+ createMockOrderPositionBook('2025-01-02'),
97
+ createMockOrderPositionBook('2025-01-03'),
98
+ ],
99
+ };
100
+ const candleMap = new Map();
101
+ const result = processOrderPositionBooks(mockData, candleMap);
102
+ expect(result).toHaveLength(3);
103
+ expect(result[0][2]).toBe(0);
104
+ expect(result[1][2]).toBe(1);
105
+ expect(result[2][2]).toBe(2);
106
+ });
107
+
108
+ it('should handle mixed cases with and without candles', () => {
109
+ const mockData: GetOrderPositionBooksQuery = {
110
+ orderPositionBooks: [
111
+ createMockOrderPositionBook('2025-01-01'),
112
+ createMockOrderPositionBook('2025-01-02'),
113
+ createMockOrderPositionBook('2025-01-03'),
114
+ ],
115
+ };
116
+ const candleMap = new Map([
117
+ ['2025-01-01', { high: 1.1111 }],
118
+ // 2025-01-02 missing from map
119
+ ['2025-01-03', { high: 1.3333 }],
120
+ ]);
121
+ const result = processOrderPositionBooks(mockData, candleMap);
122
+ expect(result).toHaveLength(3);
123
+ expect(result[0]).toEqual(['2025-01-01', 1.1111, 0]);
124
+ expect(result[1]).toEqual(['2025-01-02', null, 1]);
125
+ expect(result[2]).toEqual(['2025-01-03', 1.3333, 2]);
126
+ });
127
+ });
@@ -0,0 +1,245 @@
1
+ import { processPriceCandles } from '../../src/CrowdViewWidget/components';
2
+ import type { GetPriceCandlesQuery } from '../../src/gql/types/graphql';
3
+
4
+ describe('processPriceCandles', () => {
5
+ it('should return default values when priceCandlesData is undefined', () => {
6
+ const result = processPriceCandles(undefined);
7
+ expect(result).toEqual({
8
+ minPrice: 0,
9
+ maxPrice: 0,
10
+ hasValidCandles: false,
11
+ candleMap: new Map(),
12
+ candles: [],
13
+ });
14
+ });
15
+
16
+ it('should return default values when priceCandles is undefined', () => {
17
+ const mockData: GetPriceCandlesQuery = {
18
+ priceCandles: {} as any,
19
+ };
20
+ const result = processPriceCandles(mockData);
21
+ expect(result).toEqual({
22
+ minPrice: 0,
23
+ maxPrice: 0,
24
+ hasValidCandles: false,
25
+ candleMap: new Map(),
26
+ candles: [],
27
+ });
28
+ });
29
+
30
+ it('should return default values when candle array is empty', () => {
31
+ const mockData: GetPriceCandlesQuery = {
32
+ priceCandles: {
33
+ candle: [],
34
+ },
35
+ };
36
+ const result = processPriceCandles(mockData);
37
+ expect(result).toEqual({
38
+ minPrice: 0,
39
+ maxPrice: 0,
40
+ hasValidCandles: false,
41
+ candleMap: new Map(),
42
+ candles: [],
43
+ });
44
+ });
45
+
46
+ it('should process valid candles and calculate min/max prices', () => {
47
+ const mockData: GetPriceCandlesQuery = {
48
+ priceCandles: {
49
+ candle: [
50
+ {
51
+ point: '2025-01-01T00:00:00Z',
52
+ high: 1.5,
53
+ low: 1.0,
54
+ open: 1.2,
55
+ close: 1.3,
56
+ },
57
+ {
58
+ point: '2025-01-01T01:00:00Z',
59
+ high: 1.8,
60
+ low: 1.1,
61
+ open: 1.4,
62
+ close: 1.6,
63
+ },
64
+ {
65
+ point: '2025-01-01T02:00:00Z',
66
+ high: 1.3,
67
+ low: 0.9,
68
+ open: 1.1,
69
+ close: 1.2,
70
+ },
71
+ ],
72
+ },
73
+ };
74
+ const result = processPriceCandles(mockData);
75
+ expect(result.hasValidCandles).toBe(true);
76
+ expect(result.minPrice).toBe(0.9);
77
+ expect(result.maxPrice).toBe(1.8);
78
+ expect(result.candles).toHaveLength(3);
79
+ expect(result.candleMap.size).toBe(3);
80
+ });
81
+
82
+ it('should filter out null candles', () => {
83
+ const mockData: GetPriceCandlesQuery = {
84
+ priceCandles: {
85
+ candle: [
86
+ null,
87
+ {
88
+ point: '2025-01-01T00:00:00Z',
89
+ high: 1.5,
90
+ low: 1.0,
91
+ open: 1.2,
92
+ close: 1.3,
93
+ },
94
+ null,
95
+ {
96
+ point: '2025-01-01T01:00:00Z',
97
+ high: 1.8,
98
+ low: 1.1,
99
+ open: 1.4,
100
+ close: 1.6,
101
+ },
102
+ ],
103
+ },
104
+ };
105
+ const result = processPriceCandles(mockData);
106
+ expect(result.hasValidCandles).toBe(true);
107
+ expect(result.minPrice).toBe(1.0);
108
+ expect(result.maxPrice).toBe(1.8);
109
+ expect(result.candles).toHaveLength(4); // nulls are still in the array
110
+ expect(result.candleMap.size).toBe(2); // but not in the map
111
+ });
112
+
113
+ it('should populate candleMap with point as key', () => {
114
+ const mockData: GetPriceCandlesQuery = {
115
+ priceCandles: {
116
+ candle: [
117
+ {
118
+ point: '2025-01-01T00:00:00Z',
119
+ high: 1.5,
120
+ low: 1.0,
121
+ open: 1.2,
122
+ close: 1.3,
123
+ },
124
+ {
125
+ point: '2025-01-01T01:00:00Z',
126
+ high: 1.8,
127
+ low: 1.1,
128
+ open: 1.4,
129
+ close: 1.6,
130
+ },
131
+ ],
132
+ },
133
+ };
134
+ const result = processPriceCandles(mockData);
135
+ expect(result.candleMap.has('2025-01-01T00:00:00Z')).toBe(true);
136
+ expect(result.candleMap.has('2025-01-01T01:00:00Z')).toBe(true);
137
+ expect(result.candleMap.get('2025-01-01T00:00:00Z')).toEqual({
138
+ point: '2025-01-01T00:00:00Z',
139
+ high: 1.5,
140
+ low: 1.0,
141
+ open: 1.2,
142
+ close: 1.3,
143
+ });
144
+ });
145
+
146
+ it('should not add candles without point to candleMap', () => {
147
+ const mockData: GetPriceCandlesQuery = {
148
+ priceCandles: {
149
+ candle: [
150
+ {
151
+ point: '2025-01-01T00:00:00Z',
152
+ high: 1.5,
153
+ low: 1.0,
154
+ open: 1.2,
155
+ close: 1.3,
156
+ },
157
+ {
158
+ point: undefined as any,
159
+ high: 1.8,
160
+ low: 1.1,
161
+ open: 1.4,
162
+ close: 1.6,
163
+ },
164
+ ],
165
+ },
166
+ };
167
+ const result = processPriceCandles(mockData);
168
+ expect(result.candleMap.size).toBe(1);
169
+ expect(result.candleMap.has('2025-01-01T00:00:00Z')).toBe(true);
170
+ });
171
+
172
+ it('should handle candles with extreme price values', () => {
173
+ const mockData: GetPriceCandlesQuery = {
174
+ priceCandles: {
175
+ candle: [
176
+ {
177
+ point: '2025-01-01T00:00:00Z',
178
+ high: 1000.0,
179
+ low: 0.001,
180
+ open: 500.0,
181
+ close: 750.0,
182
+ },
183
+ ],
184
+ },
185
+ };
186
+ const result = processPriceCandles(mockData);
187
+ expect(result.minPrice).toBe(0.001);
188
+ expect(result.maxPrice).toBe(1000.0);
189
+ });
190
+
191
+ it('should handle single candle correctly', () => {
192
+ const mockData: GetPriceCandlesQuery = {
193
+ priceCandles: {
194
+ candle: [
195
+ {
196
+ point: '2025-01-01T00:00:00Z',
197
+ high: 1.5,
198
+ low: 1.0,
199
+ open: 1.2,
200
+ close: 1.3,
201
+ },
202
+ ],
203
+ },
204
+ };
205
+ const result = processPriceCandles(mockData);
206
+ expect(result.hasValidCandles).toBe(true);
207
+ expect(result.minPrice).toBe(1.0);
208
+ expect(result.maxPrice).toBe(1.5);
209
+ expect(result.candles).toHaveLength(1);
210
+ expect(result.candleMap.size).toBe(1);
211
+ });
212
+
213
+ it('should update min/max when processing candles in sequence', () => {
214
+ const mockData: GetPriceCandlesQuery = {
215
+ priceCandles: {
216
+ candle: [
217
+ {
218
+ point: '2025-01-01T00:00:00Z',
219
+ high: 2.0,
220
+ low: 1.5,
221
+ open: 1.7,
222
+ close: 1.8,
223
+ },
224
+ {
225
+ point: '2025-01-01T01:00:00Z',
226
+ high: 3.0, // new max
227
+ low: 0.5, // new min
228
+ open: 2.0,
229
+ close: 2.5,
230
+ },
231
+ {
232
+ point: '2025-01-01T02:00:00Z',
233
+ high: 1.8,
234
+ low: 1.2,
235
+ open: 1.5,
236
+ close: 1.6,
237
+ },
238
+ ],
239
+ },
240
+ };
241
+ const result = processPriceCandles(mockData);
242
+ expect(result.minPrice).toBe(0.5);
243
+ expect(result.maxPrice).toBe(3.0);
244
+ });
245
+ });