@datarailsshared/dr_renderer 1.2.453 → 1.2.455

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.
@@ -0,0 +1,1931 @@
1
+ const { DrGaugeChart, GAUGE_OPTIONS_DEFAULT } = require("../src/charts/dr_gauge_chart");
2
+ const helpers = require("../src/dr-renderer-helpers");
3
+ const { DrChartTooltip } = require("../src/dr_chart_tooltip");
4
+
5
+ jest.mock("../src/dr_chart_tooltip"); // Mock the tooltip class
6
+
7
+ const mockFormattedValue = "16,549";
8
+
9
+ DrGaugeChart.highchartsRenderer = {
10
+ CHART_TYPES: {
11
+ GAUGE_CHART_ENHANCE: "gauge-chart-enhanced",
12
+ },
13
+ getDefaultValueForChart: jest.fn(),
14
+ ptCreateElementAndDraw: jest.fn(),
15
+ ptCreateBasicLineSeries: jest.fn().mockReturnValue([{ data: [1] }, { data: [2] }]),
16
+ getSingleValueAgg: jest.fn().mockReturnValue({
17
+ aggfunc: (a, b) => a + b,
18
+ base: 0,
19
+ }),
20
+ formatValue: jest.fn().mockReturnValue({
21
+ value: mockFormattedValue,
22
+ }),
23
+ };
24
+
25
+ const mockAggregationValue = 1000;
26
+
27
+ const mockAggregation = {
28
+ widget_values_format: "###",
29
+ value: jest.fn().mockReturnValue(mockAggregationValue),
30
+ };
31
+
32
+ const mockPivotData = {
33
+ getAggregator: jest.fn().mockImplementation().mockReturnValue(mockAggregation),
34
+ };
35
+
36
+ const mockChartOptionsPercentage = {
37
+ segments: [
38
+ {
39
+ from: 0,
40
+ to: 60,
41
+ title: "Low",
42
+ color: "#BF1D30",
43
+ },
44
+ {
45
+ from: 61,
46
+ to: 80,
47
+ title: "Medium",
48
+ color: "#FFA310",
49
+ },
50
+ {
51
+ from: 81,
52
+ to: 100,
53
+ title: "High",
54
+ color: "#037C5A",
55
+ },
56
+ ],
57
+ goal: {
58
+ name: "Goal",
59
+ value: 180,
60
+ },
61
+ isAbsoluteValue: false,
62
+ label: {},
63
+ gauge: {
64
+ background: "white",
65
+ },
66
+ };
67
+
68
+ const mockChartOptionsAbsolute = {
69
+ segments: [
70
+ {
71
+ from: 0,
72
+ to: 100,
73
+ title: "Low",
74
+ color: "#BF1D30",
75
+ },
76
+ {
77
+ from: 101,
78
+ to: 200,
79
+ title: "Medium",
80
+ color: "#FFA310",
81
+ },
82
+ {
83
+ from: 201,
84
+ to: 500,
85
+ title: "High",
86
+ color: "#037C5A",
87
+ },
88
+ ],
89
+ goal: {
90
+ name: "Goal",
91
+ value: 180,
92
+ },
93
+ isAbsoluteValue: true,
94
+ };
95
+
96
+ let chart;
97
+
98
+ describe("DrGaugeChart", () => {
99
+ beforeEach(() => {
100
+ chart = new DrGaugeChart(mockPivotData, {
101
+ chartOptions: mockChartOptionsPercentage,
102
+ });
103
+ });
104
+
105
+ describe("render", () => {
106
+ it("calls configChart", () => {
107
+ chart.configChart = jest.fn().mockReturnValue({
108
+ options: "Option",
109
+ });
110
+
111
+ chart.render();
112
+ expect(chart.configChart).toHaveBeenCalled();
113
+ });
114
+ });
115
+
116
+ describe("isLeftQuarter", () => {
117
+ it("should check in which quarter the chart value is", () => {
118
+ expect(chart.isLeftQuarter(20, 100)).toBe(true);
119
+ expect(chart.isLeftQuarter(20, 30)).toBe(false);
120
+ expect(chart.isLeftQuarter(20, 20)).toBe(false);
121
+ expect(chart.isLeftQuarter(100, 200)).toBe(false);
122
+ expect(chart.isLeftQuarter(99, 200)).toBe(true);
123
+ });
124
+
125
+ it("should use default #max parameter", () => {
126
+ // Max = 180
127
+ expect(chart.isLeftQuarter(20)).toBe(true);
128
+ expect(chart.isLeftQuarter(120)).toBe(false);
129
+ });
130
+ });
131
+
132
+ describe("createPlotBands", () => {
133
+ it("should create plots for absolute values", () => {
134
+ expect(
135
+ chart.createPlotBands({
136
+ isAbsoluteValue: true,
137
+ gauge: {
138
+ thickness: 10,
139
+ },
140
+ goal: {
141
+ value: 180,
142
+ },
143
+ segments: [
144
+ {
145
+ from: 0,
146
+ to: 200,
147
+ color: "red",
148
+ title: "Title 1",
149
+ },
150
+ {
151
+ from: 201,
152
+ to: 400,
153
+ color: "blue",
154
+ title: "Title 2",
155
+ },
156
+ {
157
+ from: 401,
158
+ to: 800,
159
+ color: "blue",
160
+ title: "Title 3",
161
+ },
162
+ ],
163
+ })
164
+ ).toEqual([
165
+ {
166
+ from: 0,
167
+ to: 200,
168
+ color: "red",
169
+ thickness: 10,
170
+ title: "Title 1",
171
+ },
172
+ {
173
+ from: 200,
174
+ to: 400,
175
+ color: "blue",
176
+ thickness: 10,
177
+ title: "Title 2",
178
+ },
179
+ {
180
+ from: 400,
181
+ to: 800,
182
+ color: "blue",
183
+ thickness: 10,
184
+ title: "Title 3",
185
+ },
186
+ ]);
187
+ });
188
+
189
+ it("should clamp values (min is always 0, max of goal and last segment)", () => {
190
+ expect(
191
+ chart.createPlotBands({
192
+ isAbsoluteValue: true,
193
+ gauge: {
194
+ thickness: 10,
195
+ },
196
+ goal: {
197
+ value: 1800,
198
+ },
199
+ segments: [
200
+ {
201
+ from: 100,
202
+ to: 200,
203
+ color: "red",
204
+ title: "Title 1",
205
+ },
206
+ {
207
+ from: 201,
208
+ to: 400,
209
+ color: "blue",
210
+ title: "Title 2",
211
+ },
212
+ {
213
+ from: 401,
214
+ to: 800,
215
+ color: "blue",
216
+ title: "Title 3",
217
+ },
218
+ ],
219
+ })
220
+ ).toEqual([
221
+ {
222
+ from: 0,
223
+ to: 200,
224
+ color: "red",
225
+ thickness: 10,
226
+ title: "Title 1",
227
+ },
228
+ {
229
+ from: 200,
230
+ to: 400,
231
+ color: "blue",
232
+ thickness: 10,
233
+ title: "Title 2",
234
+ },
235
+ {
236
+ from: 400,
237
+ to: 1800,
238
+ color: "blue",
239
+ thickness: 10,
240
+ title: "Title 3",
241
+ },
242
+ ]);
243
+ });
244
+
245
+ it("should create plots for percentage values", () => {
246
+ expect(
247
+ chart.createPlotBands({
248
+ isAbsoluteValue: false,
249
+ gauge: {
250
+ thickness: 10,
251
+ },
252
+ goal: {
253
+ value: 1000,
254
+ },
255
+ segments: [
256
+ {
257
+ from: 0,
258
+ to: 50,
259
+ color: "red",
260
+ title: "Title 1",
261
+ },
262
+ {
263
+ from: 51,
264
+ to: 100,
265
+ color: "blue",
266
+ title: "Title 2",
267
+ },
268
+ {
269
+ from: 101,
270
+ to: 175,
271
+ color: "blue",
272
+ title: "Title 3",
273
+ },
274
+ ],
275
+ })
276
+ ).toEqual([
277
+ {
278
+ from: 0,
279
+ to: 500,
280
+ color: "red",
281
+ thickness: 10,
282
+ title: "Title 1",
283
+ },
284
+ {
285
+ from: 500,
286
+ to: 1000,
287
+ color: "blue",
288
+ thickness: 10,
289
+ title: "Title 2",
290
+ },
291
+ {
292
+ from: 1000,
293
+ to: 1750,
294
+ color: "blue",
295
+ thickness: 10,
296
+ title: "Title 3",
297
+ },
298
+ ]);
299
+ });
300
+ });
301
+
302
+ describe("createTicks", () => {
303
+ it("should create a ticks array", () => {
304
+ expect(
305
+ chart.createTicks(
306
+ [
307
+ {
308
+ to: 100,
309
+ },
310
+ {
311
+ to: 150,
312
+ },
313
+ {
314
+ to: 200,
315
+ },
316
+ ],
317
+ {
318
+ goal: {
319
+ value: 180,
320
+ },
321
+ }
322
+ )
323
+ ).toEqual([0, 100, 150, 180, 200]);
324
+ });
325
+
326
+ it("should sort and remove duplicates", () => {
327
+ expect(
328
+ chart.createTicks(
329
+ [
330
+ {
331
+ to: 100,
332
+ },
333
+ {
334
+ to: 100,
335
+ },
336
+ {
337
+ to: 50,
338
+ },
339
+ {
340
+ to: 180,
341
+ },
342
+ ],
343
+ {
344
+ goal: {
345
+ value: 180,
346
+ },
347
+ }
348
+ )
349
+ ).toEqual([0, 50, 100, 180]);
350
+ });
351
+ });
352
+
353
+ describe("mergeOptions", () => {
354
+ it("should merge provided options with default one", () => {
355
+ chart.getDefaultValueForChart = jest.fn().mockReturnValue({
356
+ option1: 1,
357
+ option2: 2,
358
+ });
359
+ const mockRes = {
360
+ option1: 1,
361
+ option2: 2,
362
+ };
363
+ spyOn(helpers, "mergeDeep").and.returnValue(mockRes);
364
+ expect(
365
+ chart.mergeOptions({
366
+ options3: 3,
367
+ options4: 4,
368
+ })
369
+ ).toEqual(mockRes);
370
+ expect(helpers.mergeDeep).toHaveBeenCalledWith(
371
+ GAUGE_OPTIONS_DEFAULT,
372
+ {
373
+ option1: 1,
374
+ option2: 2,
375
+ },
376
+ {
377
+ options3: 3,
378
+ options4: 4,
379
+ }
380
+ );
381
+ helpers.mergeDeep.re;
382
+ });
383
+ });
384
+
385
+ describe("getAngleForValue", () => {
386
+ it("should return an angle in radians", () => {
387
+ expect(chart.getAngleForValue(100, 0, 200, -90, 90)).toBe(Math.PI / 2);
388
+ expect(chart.getAngleForValue(50, 0, 200, -90, 90)).toBe(Math.PI / 4);
389
+ expect(chart.getAngleForValue(200, 0, 200, -90, 90)).toBe(Math.PI);
390
+ });
391
+
392
+ it("should use default value", () => {
393
+ // min = 0;
394
+ // max = 180 goal
395
+ // angle -90/ 90
396
+ expect(chart.getAngleForValue(90)).toBe(Math.PI / 2);
397
+ });
398
+ });
399
+
400
+ describe("formatValue", () => {
401
+ it("should format number to provided format", () => {
402
+ expect(chart.formatValue(16549.1234, "#,###")).toBe(mockFormattedValue);
403
+ expect(DrGaugeChart.highchartsRenderer.formatValue).toHaveBeenCalledWith("n", "#,###", 16549.1234);
404
+ });
405
+
406
+ it("should not format value unless it is a number", () => {
407
+ expect(chart.formatValue("one", "#,###")).toBe("one");
408
+ expect(DrGaugeChart.highchartsRenderer.formatValue).not.toHaveBeenCalled();
409
+ });
410
+
411
+ it("should use default value for format", () => {
412
+ expect(chart.formatValue(16549.1234)).toBe(mockFormattedValue);
413
+ expect(DrGaugeChart.highchartsRenderer.formatValue).toHaveBeenCalledWith(
414
+ "n",
415
+ mockAggregation.widget_values_format,
416
+ 16549.1234
417
+ );
418
+ });
419
+ });
420
+
421
+ describe("toPercent", () => {
422
+ it("should format absolute values", () => {
423
+ chart = new DrGaugeChart(mockPivotData, {
424
+ chartOptions: { ...mockChartOptionsAbsolute },
425
+ });
426
+ expect(chart.toPercent(100)).toBe("20%");
427
+ expect(chart.toPercent(250)).toBe("50%");
428
+ expect(chart.toPercent(500)).toBe("100%");
429
+ // rounding
430
+ expect(chart.toPercent(2)).toBe("0%");
431
+ expect(chart.toPercent(3)).toBe("1%");
432
+ });
433
+
434
+ it("should format percentage values", () => {
435
+ chart = new DrGaugeChart(mockPivotData, {
436
+ chartOptions: { ...mockChartOptionsPercentage },
437
+ });
438
+ expect(chart.toPercent(180)).toBe("100%");
439
+ expect(chart.toPercent(90)).toBe("50%");
440
+ expect(chart.toPercent(18)).toBe("10%");
441
+ // rounding
442
+ expect(chart.toPercent(0.5)).toBe("0%");
443
+ expect(chart.toPercent(2)).toBe("1%");
444
+ });
445
+ });
446
+
447
+ describe("formatValueLabel", () => {
448
+ it("should return the label html", () => {
449
+ const label = chart.formatValueLabel(100, {
450
+ label: {
451
+ font_size: 16,
452
+ font_color: "#000",
453
+ show_percentage_in_value: false,
454
+ },
455
+ gauge: {
456
+ colors: {
457
+ meta: "#808080",
458
+ },
459
+ },
460
+ });
461
+ expect(simplifyString(label)).toBe(
462
+ simplifyString(
463
+ `<span style="display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 16px;">
464
+ <span style="font-weight: 600; font-size: 1.5em; line-height: 1; color: #000">
465
+ ${mockFormattedValue}
466
+ </span>
467
+ <span style="font-weight: 500; font-size: 0.875em; line-height: 1; color: #808080;">Current status</span>
468
+ </span>`
469
+ )
470
+ );
471
+ });
472
+
473
+ it("should add percentage", () => {
474
+ const label = chart.formatValueLabel(100, {
475
+ label: {
476
+ font_size: 24,
477
+ font_color: "#000",
478
+ show_percentage_in_value: true,
479
+ },
480
+ gauge: {
481
+ colors: {
482
+ meta: "#929292",
483
+ },
484
+ },
485
+ });
486
+ expect(simplifyString(label)).toBe(
487
+ simplifyString(
488
+ `<span style="display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 24px;">
489
+ <span style="font-weight: 600; font-size: 1.5em; line-height: 1; color: #000">
490
+ ${mockFormattedValue}
491
+ <span style="font-size: 0.5833em; font-weight: 600; color: #929292;">(56%)</span>
492
+ </span>
493
+ <span style="font-weight: 500; font-size: 0.875em; line-height: 1; color: #929292;">Current status</span>
494
+ </span>`
495
+ )
496
+ );
497
+ });
498
+ });
499
+
500
+ describe("formatTickLabel", () => {
501
+ it("should return the tick label html", () => {
502
+ const label = chart.formatTickLabel(100, {
503
+ label: {
504
+ font_size: 16,
505
+ font_color: "#000",
506
+ show_percentage_in_segments: false,
507
+ },
508
+ goal: {
509
+ value: 180,
510
+ name: "Goal",
511
+ },
512
+ gauge: {
513
+ colors: {
514
+ meta: "#808080",
515
+ },
516
+ },
517
+ });
518
+
519
+ expect(simplifyString(label)).toBe(
520
+ simplifyString(`<span style="
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 4px;
524
+ font-weight: 600;
525
+ font-size: 16px;
526
+ ">
527
+ <span style="color: #000;">${mockFormattedValue}</span>
528
+ </span>`)
529
+ );
530
+ });
531
+
532
+ it("should add repcentage", () => {
533
+ const label = chart.formatTickLabel(100, {
534
+ label: {
535
+ font_size: 16,
536
+ font_color: "#000",
537
+ show_percentage_in_segments: true,
538
+ },
539
+ goal: {
540
+ value: 180,
541
+ name: "Goal",
542
+ },
543
+ gauge: {
544
+ colors: {
545
+ meta: "#808080",
546
+ },
547
+ },
548
+ });
549
+
550
+ expect(simplifyString(label)).toBe(
551
+ simplifyString(`<span style="
552
+ display: flex;
553
+ align-items: center;
554
+ gap: 4px;
555
+ font-weight: 600;
556
+ font-size: 16px;
557
+ ">
558
+ <span style="color: #000;">${mockFormattedValue}</span>
559
+ <span style="font-size: 0.75em; color: #808080; font-weight: 400;">(56%)</span>
560
+ </span>`)
561
+ );
562
+ });
563
+
564
+ it("should apply extra styles for goal", () => {
565
+ const label = chart.formatTickLabel(180, {
566
+ label: {
567
+ font_size: 16,
568
+ font_color: "#000",
569
+ show_percentage_in_segments: true,
570
+ },
571
+ goal: {
572
+ value: 180,
573
+ name: "Goal",
574
+ },
575
+ gauge: {
576
+ colors: {
577
+ meta: "#808080",
578
+ goal: "#ff0",
579
+ },
580
+ },
581
+ });
582
+
583
+ expect(simplifyString(label)).toBe(
584
+ simplifyString(`<span style="
585
+ display: flex;
586
+ align-items: center;
587
+ gap: 4px;
588
+ font-weight: 600;
589
+ font-size: 16px;
590
+ padding-left: 12px;
591
+ ">
592
+ <span style="font-size: 1.125em; color: #ff0;">${mockFormattedValue}</span>
593
+ <span style="font-size: 0.75em; color: #808080; font-weight: 400;">(100%)</span>
594
+ </span>`)
595
+ );
596
+ });
597
+
598
+ it("should add goal title", () => {
599
+ const label = chart.formatTickLabel(180, {
600
+ label: {
601
+ font_size: 16,
602
+ font_color: "#000",
603
+ show_percentage_in_segments: true,
604
+ show_goal_name: true,
605
+ },
606
+ goal: {
607
+ value: 180,
608
+ title: "Goal",
609
+ },
610
+ gauge: {
611
+ colors: {
612
+ meta: "#808080",
613
+ goal: "#ff0",
614
+ },
615
+ },
616
+ });
617
+
618
+ expect(simplifyString(label)).toBe(
619
+ simplifyString(`<span style="
620
+ display: flex;
621
+ align-items: center;
622
+ gap: 4px;
623
+ font-weight: 600;
624
+ font-size: 16px;
625
+ padding-left: 12px;
626
+ ">
627
+ <span style="font-size: 1.125em; color: #ff0;">${mockFormattedValue}</span>
628
+ <span style="font-size: 0.75em; color: #ff0;">Goal</span>
629
+ <span style="font-size: 0.75em; color: #808080; font-weight: 400;">(100%)</span>
630
+ </span>`)
631
+ );
632
+ });
633
+
634
+ it("should add paddings", () => {
635
+ spyOn(chart, "isLeftQuarter").and.returnValues(true, false);
636
+
637
+ const labelLeft = chart.formatTickLabel(180, {
638
+ label: {
639
+ font_size: 16,
640
+ font_color: "#000",
641
+ show_percentage_in_segments: false,
642
+ show_goal_name: false,
643
+ },
644
+ goal: {
645
+ value: 180,
646
+ title: "Goal",
647
+ },
648
+ gauge: {
649
+ colors: {
650
+ meta: "#808080",
651
+ goal: "#ff0",
652
+ },
653
+ },
654
+ });
655
+
656
+ expect(simplifyString(labelLeft)).toBe(
657
+ simplifyString(`<span style="
658
+ display: flex;
659
+ align-items: center;
660
+ gap: 4px;
661
+ font-weight: 600;
662
+ font-size: 16px;
663
+ padding-right: 12px;
664
+ ">
665
+ <span style="font-size: 1.125em; color: #ff0;">${mockFormattedValue}</span>
666
+ </span>`)
667
+ );
668
+
669
+ const labelRight = chart.formatTickLabel(180, {
670
+ label: {
671
+ font_size: 16,
672
+ font_color: "#000",
673
+ show_percentage_in_segments: false,
674
+ show_goal_name: false,
675
+ },
676
+ goal: {
677
+ value: 180,
678
+ title: "Goal",
679
+ },
680
+ gauge: {
681
+ colors: {
682
+ meta: "#808080",
683
+ goal: "#ff0",
684
+ },
685
+ },
686
+ });
687
+
688
+ expect(simplifyString(labelRight)).toBe(
689
+ simplifyString(`<span style="
690
+ display: flex;
691
+ align-items: center;
692
+ gap: 4px;
693
+ font-weight: 600;
694
+ font-size: 16px;
695
+ padding-left: 12px;
696
+ ">
697
+ <span style="font-size: 1.125em; color: #ff0;">${mockFormattedValue}</span>
698
+ </span>`)
699
+ );
700
+ });
701
+ });
702
+
703
+ describe("getValue", () => {
704
+ it("should return aggregator value unledss the total is empty", () => {
705
+ DrGaugeChart.highchartsRenderer.ptCreateBasicLineSeries.mockReturnValue([]);
706
+ expect(chart.getValue(mockPivotData, {})).toBe(mockAggregationValue);
707
+ });
708
+
709
+ it("should return value to display", () => {
710
+ DrGaugeChart.highchartsRenderer.ptCreateBasicLineSeries.mockReturnValue([{ data: [2000] }]);
711
+ expect(chart.getValue(mockPivotData, {})).toBe(2000);
712
+ });
713
+
714
+ it("should calculate total", () => {
715
+ DrGaugeChart.highchartsRenderer.ptCreateBasicLineSeries.mockReturnValue([
716
+ { data: [2000] },
717
+ { data: [3000] },
718
+ { data: [5000] },
719
+ ]);
720
+ expect(chart.getValue(mockPivotData, {})).toBe(10000);
721
+ });
722
+ });
723
+
724
+ describe("getBorderPosition", () => {
725
+ const mockChart = {
726
+ pane: [
727
+ {
728
+ options: {
729
+ center: [200, 200],
730
+ size: 100,
731
+ },
732
+ },
733
+ ],
734
+ };
735
+
736
+ const mockOptions = {
737
+ gauge: {
738
+ tickLength: 40,
739
+ tickWidth: 2,
740
+ },
741
+ };
742
+
743
+ it("return a start border position", () => {
744
+ expect(chart.getBorderPosition(mockChart, mockOptions, "start")).toEqual({
745
+ x: 170,
746
+ y: 201,
747
+ });
748
+ });
749
+
750
+ it("return an end border position", () => {
751
+ expect(chart.getBorderPosition(mockChart, mockOptions, "end")).toEqual({
752
+ x: 230,
753
+ y: 201,
754
+ });
755
+ });
756
+ });
757
+
758
+ describe("createBorder", () => {
759
+ beforeEach(() => {
760
+ chart.getBorderPosition = jest.fn().mockReturnValue({
761
+ x: 100,
762
+ y: 200,
763
+ });
764
+ });
765
+
766
+ const toFrontMock = jest.fn();
767
+
768
+ const addMock = jest.fn().mockReturnValue({
769
+ toFront: toFrontMock,
770
+ });
771
+
772
+ const attrMock = jest.fn().mockReturnValue({
773
+ add: addMock,
774
+ });
775
+
776
+ const arcMock = jest.fn().mockReturnValue({
777
+ attr: attrMock,
778
+ });
779
+
780
+ const mockChart = {
781
+ renderer: {
782
+ arc: arcMock,
783
+ },
784
+ yAxis: [
785
+ {
786
+ options: {
787
+ plotBands: [
788
+ {
789
+ color: "red",
790
+ },
791
+ {
792
+ color: "blue",
793
+ },
794
+ {
795
+ color: "green",
796
+ },
797
+ ],
798
+ },
799
+ },
800
+ ],
801
+ };
802
+
803
+ it("should create a start border", () => {
804
+ chart.createBorder(
805
+ mockChart,
806
+ {
807
+ gauge: {
808
+ thickness: 10,
809
+ },
810
+ },
811
+ "start"
812
+ );
813
+ // draw an arc
814
+ expect(arcMock).toHaveBeenCalledWith({
815
+ x: 100,
816
+ y: 200,
817
+ r: 5,
818
+ innerR: 0,
819
+ start: 0,
820
+ end: Math.PI,
821
+ });
822
+ // set color of first segment
823
+ expect(attrMock).toHaveBeenCalledWith({
824
+ fill: "red",
825
+ });
826
+ //add to pane
827
+ expect(addMock).toHaveBeenCalled();
828
+ // move to front
829
+ expect(toFrontMock).toHaveBeenCalled();
830
+ });
831
+
832
+ it("should create an end border", () => {
833
+ chart.createBorder(
834
+ mockChart,
835
+ {
836
+ gauge: {
837
+ thickness: 20,
838
+ },
839
+ },
840
+ "end"
841
+ );
842
+ // draw an arc
843
+ expect(arcMock).toHaveBeenCalledWith({
844
+ x: 100,
845
+ y: 200,
846
+ r: 10,
847
+ innerR: 0,
848
+ start: 0,
849
+ end: Math.PI,
850
+ });
851
+ // set color of last segment
852
+ expect(attrMock).toHaveBeenCalledWith({
853
+ fill: "green",
854
+ });
855
+ //add to pane
856
+ expect(addMock).toHaveBeenCalled();
857
+ // move to front
858
+ expect(toFrontMock).toHaveBeenCalled();
859
+ });
860
+ });
861
+
862
+ describe("getGoalIconPosition", () => {
863
+ it("should return an icon position", () => {
864
+ chart.getAngleForValue = jest.fn().mockReturnValue(0);
865
+ expect(
866
+ chart.getGoalIconPosition(
867
+ {
868
+ pane: [
869
+ {
870
+ options: {
871
+ center: [100, 100],
872
+ size: 100,
873
+ },
874
+ },
875
+ ],
876
+ },
877
+ {
878
+ gauge: {
879
+ goalIconSize: [16, 16],
880
+ },
881
+ goal: {
882
+ value: 0,
883
+ },
884
+ }
885
+ )
886
+ ).toEqual({
887
+ x: 42,
888
+ y: 92,
889
+ });
890
+ expect(chart.getAngleForValue).toHaveBeenCalledWith(0);
891
+ chart.getAngleForValue = jest.fn().mockReturnValue(Math.PI / 2);
892
+ expect(
893
+ chart.getGoalIconPosition(
894
+ {
895
+ pane: [
896
+ {
897
+ options: {
898
+ center: [200, 200],
899
+ size: 200,
900
+ },
901
+ },
902
+ ],
903
+ },
904
+ {
905
+ gauge: {
906
+ goalIconSize: [24, 24],
907
+ },
908
+ goal: {
909
+ value: 200,
910
+ },
911
+ }
912
+ )
913
+ ).toEqual({
914
+ x: 188,
915
+ y: 88,
916
+ });
917
+ expect(chart.getAngleForValue).toHaveBeenCalledWith(200);
918
+ expect(
919
+ chart.getGoalIconPosition(
920
+ {
921
+ pane: [
922
+ {
923
+ options: {
924
+ center: [500, 500],
925
+ size: 500,
926
+ },
927
+ },
928
+ ],
929
+ },
930
+ {
931
+ gauge: {
932
+ goalIconSize: [50, 50],
933
+ },
934
+ goal: {
935
+ value: 500,
936
+ },
937
+ }
938
+ )
939
+ ).toEqual({
940
+ x: 475,
941
+ y: 225,
942
+ });
943
+ expect(chart.getAngleForValue).toHaveBeenCalledWith(500);
944
+ });
945
+ });
946
+
947
+ describe("createGoalIcon", () => {
948
+ const toFront = jest.fn();
949
+ const add = jest.fn().mockReturnValue({ toFront });
950
+ const image = jest.fn().mockReturnValue({ add });
951
+
952
+ beforeEach(() => {
953
+ chart.getGoalIconPosition = jest.fn().mockReturnValue({
954
+ x: 100,
955
+ y: 200,
956
+ });
957
+
958
+ chart.createGoalIcon(
959
+ {
960
+ renderer: {
961
+ image,
962
+ },
963
+ },
964
+ {
965
+ gauge: {
966
+ goalIcon: "base64hash",
967
+ goalIconSize: [16, 16],
968
+ },
969
+ }
970
+ );
971
+ });
972
+
973
+ it("should create an goal icon", () => {
974
+ expect(image).toHaveBeenCalledWith("base64hash", 100, 200, 16, 16);
975
+ });
976
+
977
+ it("should add icon to the plot", () => {
978
+ expect(add).toHaveBeenCalled();
979
+ });
980
+
981
+ it("should move icon to front", () => {
982
+ expect(toFront).toHaveBeenCalled();
983
+ });
984
+ });
985
+
986
+ describe("getValueLabelPosition", () => {
987
+ it("should return a value label position depends on the pane center and offsets", () => {
988
+ expect(
989
+ chart.getValueLabelPosition(
990
+ {
991
+ pane: [
992
+ {
993
+ options: {
994
+ center: [500, 300],
995
+ },
996
+ },
997
+ ],
998
+ },
999
+ {
1000
+ gauge: {
1001
+ valueOffset: [20, 30, 40, 30],
1002
+ },
1003
+ }
1004
+ )
1005
+ ).toEqual({
1006
+ x: 500,
1007
+ y: 320,
1008
+ });
1009
+
1010
+ expect(
1011
+ chart.getValueLabelPosition(
1012
+ {
1013
+ pane: [
1014
+ {
1015
+ options: {
1016
+ center: [200, 100],
1017
+ },
1018
+ },
1019
+ ],
1020
+ },
1021
+ {
1022
+ gauge: {
1023
+ valueOffset: [40, 30, 40, 30],
1024
+ },
1025
+ }
1026
+ )
1027
+ ).toEqual({
1028
+ x: 200,
1029
+ y: 140,
1030
+ });
1031
+ });
1032
+ });
1033
+
1034
+ describe("createValueLabel", () => {
1035
+ const css = jest.fn().mockReturnValue({});
1036
+ const attr = jest.fn().mockReturnValue({ css });
1037
+ const toFront = jest.fn().mockReturnValue({ attr });
1038
+ const add = jest.fn().mockReturnValue({ toFront });
1039
+ const text = jest.fn().mockReturnValue({ add });
1040
+
1041
+ beforeEach(() => {
1042
+ chart.formatValueLabel = jest.fn().mockReturnValue("<div>Mock label</div>");
1043
+ chart.getValueLabelPosition = jest.fn().mockReturnValue({
1044
+ x: 100,
1045
+ y: 200,
1046
+ });
1047
+ chart.createValueLabel({
1048
+ renderer: {
1049
+ text,
1050
+ },
1051
+ });
1052
+ });
1053
+
1054
+ it("should create a value label", () => {
1055
+ // create label
1056
+ expect(text).toHaveBeenCalledWith("<div>Mock label</div>", 0, 0, true);
1057
+ // set position
1058
+ expect(attr).toHaveBeenCalledWith({
1059
+ x: 100,
1060
+ y: 200,
1061
+ });
1062
+ // centering
1063
+ expect(css).toHaveBeenCalledWith({ transform: "translateX(-50%)" });
1064
+ });
1065
+ });
1066
+
1067
+ describe("getPaneDimensions", () => {
1068
+ const mockChart = {
1069
+ chartWidth: 500,
1070
+ chartHeight: 500,
1071
+ renderer: {
1072
+ label: jest.fn().mockReturnValue({
1073
+ add: jest.fn().mockReturnValue({
1074
+ destroy: jest.fn(),
1075
+ bBox: {
1076
+ width: 50,
1077
+ height: 25,
1078
+ },
1079
+ }),
1080
+ }),
1081
+ },
1082
+ };
1083
+
1084
+ const mockOptions = {
1085
+ gauge: {
1086
+ offset: [30, 30, 30, 30],
1087
+ valueOffset: [20, 20, 20, 20],
1088
+ goalIconSize: [16, 16],
1089
+ },
1090
+ label: {
1091
+ show: true,
1092
+ },
1093
+ goal: {
1094
+ value: 100,
1095
+ },
1096
+ };
1097
+
1098
+ beforeEach(() => {
1099
+ chart.createValueLabel = jest.fn().mockReturnValue({
1100
+ getBBox: jest.fn().mockReturnValue({
1101
+ height: 50,
1102
+ }),
1103
+ destroy: jest.fn(),
1104
+ });
1105
+ });
1106
+
1107
+ it("should calculate a pane dimensions (labels - reserve space - horizontal)", () => {
1108
+ chart.getAngleForValue = jest.fn().mockReturnValue(0);
1109
+ expect(chart.getPaneDimensions(mockChart, mockOptions)).toEqual({
1110
+ center: [250, 380],
1111
+ radius: 170,
1112
+ });
1113
+ });
1114
+
1115
+ it("should calculate a pane dimensions (labels - reserve space - )", () => {
1116
+ chart.getAngleForValue = jest.fn().mockReturnValue(Math.PI / 4);
1117
+
1118
+ expect(chart.getPaneDimensions(mockChart, mockOptions)).toEqual({
1119
+ center: [250, 380],
1120
+ radius: 220,
1121
+ });
1122
+ });
1123
+
1124
+ it("should calculate a pane dimensions (labels - not reserve space - vertical)", () => {
1125
+ chart.getAngleForValue = jest.fn().mockReturnValue(Math.PI / 2);
1126
+
1127
+ expect(chart.getPaneDimensions({ ...mockChart, ...{ chartWidth: 1000 } }, mockOptions)).toEqual({
1128
+ center: [500, 380],
1129
+ radius: 337.5,
1130
+ });
1131
+ });
1132
+
1133
+ it("should calculate a pane dimensions (goal - reserve space - horizontal)", () => {
1134
+ chart.getAngleForValue = jest.fn().mockReturnValue(0);
1135
+ expect(
1136
+ chart.getPaneDimensions(mockChart, {
1137
+ ...mockOptions,
1138
+ ...{
1139
+ label: {
1140
+ show: false,
1141
+ },
1142
+ },
1143
+ })
1144
+ ).toEqual({
1145
+ center: [250, 380],
1146
+ radius: 212,
1147
+ });
1148
+ });
1149
+
1150
+ it("should calculate a pane dimensions (goal - not reserve space)", () => {
1151
+ chart.getAngleForValue = jest.fn().mockReturnValue(Math.PI / 4);
1152
+ expect(
1153
+ chart.getPaneDimensions(mockChart, {
1154
+ ...mockOptions,
1155
+ ...{
1156
+ label: {
1157
+ show: false,
1158
+ },
1159
+ },
1160
+ })
1161
+ ).toEqual({
1162
+ center: [250, 380],
1163
+ radius: 220,
1164
+ });
1165
+ });
1166
+
1167
+ it("should calculate a pane dimensions (goal - reserve space - vertical)", () => {
1168
+ chart.getAngleForValue = jest.fn().mockReturnValue(Math.PI / 2);
1169
+ expect(
1170
+ chart.getPaneDimensions(
1171
+ { ...mockChart, ...{ chartWidth: 1000 } },
1172
+ {
1173
+ ...mockOptions,
1174
+ ...{
1175
+ label: {
1176
+ show: false,
1177
+ },
1178
+ },
1179
+ }
1180
+ )
1181
+ ).toEqual({
1182
+ center: [500, 380],
1183
+ radius: 342,
1184
+ });
1185
+ });
1186
+ });
1187
+
1188
+ describe("setTicksStyles", () => {
1189
+ it("should align ticks on the left", () => {
1190
+ const mockChart = {
1191
+ yAxis: [
1192
+ {
1193
+ ticks: {
1194
+ 0: {
1195
+ pos: 30,
1196
+ css: jest.fn(),
1197
+ label: {
1198
+ css: jest.fn(),
1199
+ },
1200
+ },
1201
+ 1: {
1202
+ pos: 120,
1203
+ label: {
1204
+ css: jest.fn(),
1205
+ },
1206
+ },
1207
+ },
1208
+ },
1209
+ ],
1210
+ };
1211
+
1212
+ chart.setTicksStyles(mockChart, {
1213
+ gauge: {
1214
+ colors: {
1215
+ goal: "blue",
1216
+ },
1217
+ },
1218
+ goal: {
1219
+ value: 180,
1220
+ },
1221
+ });
1222
+ // move tick on the left
1223
+ expect(mockChart.yAxis[0].ticks[0].label.css).toHaveBeenCalledWith({
1224
+ transform: "translate(-100%, 0)",
1225
+ });
1226
+ // do not move tick on the right
1227
+ expect(mockChart.yAxis[0].ticks[1].label.css).toHaveBeenCalledWith({
1228
+ transform: "translate(0, 0)",
1229
+ });
1230
+ });
1231
+
1232
+ it("should align ticks on the left", () => {
1233
+ const mockChart = {
1234
+ yAxis: [
1235
+ {
1236
+ ticks: {
1237
+ 0: {
1238
+ pos: 180,
1239
+ mark: {
1240
+ attr: jest.fn(),
1241
+ },
1242
+ },
1243
+ },
1244
+ },
1245
+ ],
1246
+ };
1247
+
1248
+ chart.setTicksStyles(mockChart, {
1249
+ gauge: {
1250
+ colors: {
1251
+ goal: "blue",
1252
+ },
1253
+ },
1254
+ goal: {
1255
+ value: 180,
1256
+ },
1257
+ });
1258
+ // add a goal tick styles
1259
+ expect(mockChart.yAxis[0].ticks[0].mark.attr).toHaveBeenCalledWith({
1260
+ "pointer-events": "none",
1261
+ stroke: "blue",
1262
+ "stroke-dasharray": 2,
1263
+ });
1264
+ });
1265
+ });
1266
+
1267
+ describe("addTooltips", () => {
1268
+ let mockChart;
1269
+ let mockOptions;
1270
+ let mockInstance;
1271
+
1272
+ beforeEach(() => {
1273
+ mockChart = {
1274
+ yAxis: [
1275
+ {
1276
+ ticks: {
1277
+ 0: { pos: 0, label: { element: "label0" } },
1278
+ 50: { pos: 50, label: { element: "label50" } },
1279
+ 100: { pos: 100, label: { element: "label100" } },
1280
+ },
1281
+ plotLinesAndBands: [
1282
+ {
1283
+ options: { title: "Segment 1" },
1284
+ svgElem: { element: "bandElement1" },
1285
+ },
1286
+ {
1287
+ options: { title: "Segment 2" },
1288
+ svgElem: { element: "bandElement2" },
1289
+ },
1290
+ ],
1291
+ },
1292
+ ],
1293
+ label: { element: "labelElement" },
1294
+ goalIcon: { element: "goalIconElement" },
1295
+ };
1296
+
1297
+ mockOptions = {
1298
+ tooltips: {
1299
+ show: true,
1300
+ font_size: 16,
1301
+ font_color: "black",
1302
+ font_style: "Poppins",
1303
+ },
1304
+ label: {
1305
+ show: true,
1306
+ },
1307
+ goal: { value: 100, title: "Goal" },
1308
+ };
1309
+
1310
+ // Mock DrChartTooltip instance
1311
+ mockInstance = {
1312
+ add: jest.fn(),
1313
+ };
1314
+ DrChartTooltip.mockImplementation(() => mockInstance);
1315
+
1316
+ // Reset mocks
1317
+ jest.clearAllMocks();
1318
+ });
1319
+
1320
+ afterEach(() => {
1321
+ jest.restoreAllMocks();
1322
+ });
1323
+
1324
+ it("should return false unless tooltips is on", () => {
1325
+ mockOptions.tooltips.show = false;
1326
+ expect(chart.addTooltips(mockChart, mockOptions)).toBe(false);
1327
+ });
1328
+
1329
+ it("should initialize DrChartTooltip with correct options", () => {
1330
+ chart.addTooltips(mockChart, mockOptions);
1331
+
1332
+ expect(DrChartTooltip).toHaveBeenCalledWith(mockChart, {
1333
+ fontSize: 16,
1334
+ fontFamily: "Poppins",
1335
+ color: "black",
1336
+ });
1337
+ });
1338
+
1339
+ it("adds tooltips to plotLinesAndBands when show_segment_name is true", () => {
1340
+ mockOptions.tooltips.show_segment_name = true;
1341
+
1342
+ chart.addTooltips(mockChart, mockOptions);
1343
+
1344
+ expect(mockInstance.add).toHaveBeenCalledWith("Segment 1", "bandElement1", { direction: "top", followPointer: true });
1345
+ expect(mockInstance.add).toHaveBeenCalledWith("Segment 2", "bandElement2", { direction: "top", followPointer: true });
1346
+ });
1347
+
1348
+ it("adds percentage tooltip to chart label when show_percentage_in_value is true", () => {
1349
+ mockOptions.tooltips.show_percentage_in_value = true;
1350
+ const toPercentSpy = jest.spyOn(chart, "toPercent").mockReturnValue("50%");
1351
+
1352
+ chart.addTooltips(mockChart, mockOptions);
1353
+
1354
+ expect(mockInstance.add).toHaveBeenCalledWith("50%", "labelElement", { direction: "top" });
1355
+
1356
+ toPercentSpy.mockRestore();
1357
+ });
1358
+
1359
+ it("does not add percentage tooltip to chart label when it is shown in label", () => {
1360
+ mockOptions.tooltips.show_percentage_in_value = true;
1361
+ mockOptions.label.show_percentage_in_value = true;
1362
+
1363
+ chart.addTooltips(mockChart, mockOptions);
1364
+
1365
+ expect(mockInstance.add).not.toHaveBeenCalled();
1366
+ });
1367
+
1368
+ it("adds goal tooltip when goal value matches tick position and labels are hidden", () => {
1369
+ mockOptions.label.show_goal_name = true;
1370
+ mockOptions.label.show = false;
1371
+ jest.spyOn(chart, "formatValue").mockReturnValue("100%");
1372
+
1373
+ chart.addTooltips(mockChart, mockOptions);
1374
+
1375
+ expect(mockInstance.add).toHaveBeenCalledWith('Goal<span style="font-weight: 600">100%</span>', "goalIconElement", {
1376
+ direction: "right",
1377
+ });
1378
+ });
1379
+
1380
+ it("adds goal tooltip when goal value matches tick position and labels are hidden (without goal title)", () => {
1381
+ mockOptions.label.show_goal_name = false;
1382
+ mockOptions.label.show = false;
1383
+ jest.spyOn(chart, "formatValue").mockReturnValue("100%");
1384
+
1385
+ chart.addTooltips(mockChart, mockOptions);
1386
+
1387
+ expect(mockInstance.add).toHaveBeenCalledWith('<span style="font-weight: 600">100%</span>', "goalIconElement", {
1388
+ direction: "right",
1389
+ });
1390
+ });
1391
+
1392
+ it("adds goal tooltip when goal value matches tick position and labels are hidden (undefined goal title)", () => {
1393
+ mockOptions.label.show_goal_name = true;
1394
+ mockOptions.goal.title = undefined;
1395
+ mockOptions.label.show = false;
1396
+ jest.spyOn(chart, "formatValue").mockReturnValue("100%");
1397
+
1398
+ chart.addTooltips(mockChart, mockOptions);
1399
+
1400
+ expect(mockInstance.add).toHaveBeenCalledWith('<span style="font-weight: 600">100%</span>', "goalIconElement", {
1401
+ direction: "right",
1402
+ });
1403
+ });
1404
+
1405
+ it("adds goal tooltip when goal value matches tick position and labels are hidden (left direction)", () => {
1406
+ mockOptions.label.show_goal_name = true;
1407
+ mockOptions.label.show = false;
1408
+ jest.spyOn(chart, "isLeftQuarter").mockReturnValue(true);
1409
+ jest.spyOn(chart, "formatValue").mockReturnValue("100%");
1410
+
1411
+ chart.addTooltips(mockChart, mockOptions);
1412
+
1413
+ expect(mockInstance.add).toHaveBeenCalledWith('Goal<span style="font-weight: 600">100%</span>', "goalIconElement", {
1414
+ direction: "left",
1415
+ });
1416
+ });
1417
+
1418
+ it("adds percentage tooltip to segment labels when show_percentage_in_segments is true", () => {
1419
+ jest.spyOn(chart, "toPercent").mockReturnValue("100%");
1420
+ mockOptions.tooltips.show_percentage_in_segments = true;
1421
+ mockOptions.label.show_percentage_in_segments = false;
1422
+ chart.addTooltips(mockChart, mockOptions);
1423
+
1424
+ expect(mockInstance.add).toHaveBeenCalledWith("100%", "label0", { direction: "left" });
1425
+ expect(mockInstance.add).toHaveBeenCalledWith("100%", "label50", { direction: "left" });
1426
+ expect(mockInstance.add).toHaveBeenCalledWith("100%", "label100", { direction: "right" });
1427
+ });
1428
+ });
1429
+
1430
+ describe("setPane", () => {
1431
+ let mockChart;
1432
+ let mockOptions;
1433
+
1434
+ beforeEach(() => {
1435
+ // Mock the chart object
1436
+ mockChart = {
1437
+ pane: [{ options: { size: null, center: null } }],
1438
+ yAxis: [
1439
+ {
1440
+ options: {
1441
+ plotBands: [{ outerRadius: null }, { outerRadius: null }],
1442
+ },
1443
+ },
1444
+ ],
1445
+ series: [{
1446
+ options: {
1447
+ dial: {
1448
+ radius: '80%'
1449
+ }
1450
+ }
1451
+ }]
1452
+ };
1453
+
1454
+ // Mock options
1455
+ mockOptions = {
1456
+ gauge: { tickLength: 20, thickness: 10 },
1457
+ };
1458
+
1459
+ // Mock getPaneDimensions to return specific values
1460
+ jest.spyOn(chart, "getPaneDimensions").mockReturnValue({
1461
+ radius: 100,
1462
+ center: ["50%", "50%"],
1463
+ });
1464
+ });
1465
+
1466
+ it("calls getPaneDimensions with correct arguments", () => {
1467
+ const getPaneDimensionsSpy = jest.spyOn(chart, "getPaneDimensions");
1468
+
1469
+ chart.setPane(mockChart, mockOptions);
1470
+
1471
+ expect(getPaneDimensionsSpy).toHaveBeenCalledWith(mockChart, mockOptions);
1472
+ });
1473
+
1474
+ it("updates the pane size and center based on dimensions", () => {
1475
+ chart.setPane(mockChart, mockOptions);
1476
+
1477
+ expect(mockChart.pane[0].options.size).toBe(200); // 2 * radius
1478
+ expect(mockChart.pane[0].options.center).toEqual(["50%", "50%"]);
1479
+ });
1480
+
1481
+ it("updates the outerRadius of plot bands correctly", () => {
1482
+ chart.setPane(mockChart, mockOptions);
1483
+
1484
+ const expectedOuterRadius = 100 - (20 - 10) / 2; // radius - (tickLength - thickness) / 2
1485
+ mockChart.yAxis[0].options.plotBands.forEach((band) => {
1486
+ expect(band.outerRadius).toBe(expectedOuterRadius);
1487
+ });
1488
+ });
1489
+
1490
+ it("updates the dial radius", () => {
1491
+ chart.setPane(mockChart, mockOptions);
1492
+
1493
+ const expectedDialRadius = Math.round(100 * (100 - 20 - 10) / 100) + '%'; // radius - tickLength - offset) / radius
1494
+ expect(mockChart.series[0].options.dial.radius).toBe(expectedDialRadius);
1495
+ });
1496
+
1497
+ afterEach(() => {
1498
+ jest.restoreAllMocks();
1499
+ });
1500
+ });
1501
+
1502
+ describe("setCustomElements", () => {
1503
+ let mockChart;
1504
+ let mockOptions;
1505
+ let mockValueLabel;
1506
+ let mockStartBorder;
1507
+ let mockEndBorder;
1508
+ let mockGoalIcon;
1509
+
1510
+ beforeEach(() => {
1511
+ // Mock chart object
1512
+ mockChart = {};
1513
+
1514
+ // Mock options
1515
+ mockOptions = {
1516
+ someOption: "value", // Add relevant options here
1517
+ };
1518
+
1519
+ // Mock custom elements
1520
+ mockValueLabel = { type: "valueLabel" };
1521
+ mockStartBorder = { type: "startBorder" };
1522
+ mockEndBorder = { type: "endBorder" };
1523
+ mockGoalIcon = { type: "goalIcon" };
1524
+
1525
+ // Spy on class methods
1526
+ jest.spyOn(chart, "createValueLabel").mockReturnValue(mockValueLabel);
1527
+ jest.spyOn(chart, "createBorder").mockImplementation((chart, options, position) => {
1528
+ if (position === "start") return mockStartBorder;
1529
+ if (position === "end") return mockEndBorder;
1530
+ });
1531
+ jest.spyOn(chart, "createGoalIcon").mockReturnValue(mockGoalIcon);
1532
+ });
1533
+
1534
+ afterEach(() => {
1535
+ jest.restoreAllMocks();
1536
+ });
1537
+
1538
+ it("assigns the value label to chart.label", () => {
1539
+ chart.setCustomElements(mockChart, mockOptions);
1540
+
1541
+ expect(chart.createValueLabel).toHaveBeenCalledWith(mockChart, mockOptions);
1542
+ expect(mockChart.label).toBe(mockValueLabel);
1543
+ });
1544
+
1545
+ it("assigns the start border to chart.startBorder", () => {
1546
+ chart.setCustomElements(mockChart, mockOptions);
1547
+
1548
+ expect(chart.createBorder).toHaveBeenCalledWith(mockChart, mockOptions, "start");
1549
+ expect(mockChart.startBorder).toBe(mockStartBorder);
1550
+ });
1551
+
1552
+ it("assigns the end border to chart.endBorder", () => {
1553
+ chart.setCustomElements(mockChart, mockOptions);
1554
+
1555
+ expect(chart.createBorder).toHaveBeenCalledWith(mockChart, mockOptions, "end");
1556
+ expect(mockChart.endBorder).toBe(mockEndBorder);
1557
+ });
1558
+
1559
+ it("assigns the goal icon to chart.goalIcon", () => {
1560
+ chart.setCustomElements(mockChart, mockOptions);
1561
+
1562
+ expect(chart.createGoalIcon).toHaveBeenCalledWith(mockChart, mockOptions);
1563
+ expect(mockChart.goalIcon).toBe(mockGoalIcon);
1564
+ });
1565
+ });
1566
+
1567
+ describe("updateCustomElements", () => {
1568
+ let mockChart;
1569
+ let mockOptions;
1570
+ let mockStartPosition;
1571
+ let mockEndPosition;
1572
+ let mockGoalIconPosition;
1573
+ let mockValueLabelPosition;
1574
+
1575
+ beforeEach(() => {
1576
+ // Mock chart object with elements having .attr method
1577
+ mockChart = {
1578
+ startBorder: { attr: jest.fn() },
1579
+ endBorder: { attr: jest.fn() },
1580
+ goalIcon: { attr: jest.fn() },
1581
+ label: { attr: jest.fn() },
1582
+ };
1583
+
1584
+ // Mock options
1585
+ mockOptions = {
1586
+ someOption: "value", // Add relevant options here
1587
+ };
1588
+
1589
+ // Mock positions returned by helper methods
1590
+ mockStartPosition = { x: 10, y: 20 };
1591
+ mockEndPosition = { x: 30, y: 40 };
1592
+ mockGoalIconPosition = { x: 50, y: 60 };
1593
+ mockValueLabelPosition = { x: 70, y: 80 };
1594
+
1595
+ // Spy on helper methods
1596
+ jest.spyOn(chart, "getBorderPosition").mockImplementation((chart, options, position) => {
1597
+ if (position === "start") return mockStartPosition;
1598
+ if (position === "end") return mockEndPosition;
1599
+ });
1600
+ jest.spyOn(chart, "getGoalIconPosition").mockReturnValue(mockGoalIconPosition);
1601
+ jest.spyOn(chart, "getValueLabelPosition").mockReturnValue(mockValueLabelPosition);
1602
+ });
1603
+
1604
+ afterEach(() => {
1605
+ jest.restoreAllMocks();
1606
+ });
1607
+
1608
+ it("updates startBorder attributes correctly", () => {
1609
+ chart.updateCustomElements(mockChart, mockOptions);
1610
+
1611
+ expect(chart.getBorderPosition).toHaveBeenCalledWith(mockChart, mockOptions, "start");
1612
+ expect(mockChart.startBorder.attr).toHaveBeenCalledWith(mockStartPosition);
1613
+ });
1614
+
1615
+ it("updates endBorder attributes correctly", () => {
1616
+ chart.updateCustomElements(mockChart, mockOptions);
1617
+
1618
+ expect(chart.getBorderPosition).toHaveBeenCalledWith(mockChart, mockOptions, "end");
1619
+ expect(mockChart.endBorder.attr).toHaveBeenCalledWith(mockEndPosition);
1620
+ });
1621
+
1622
+ it("updates goalIcon attributes correctly", () => {
1623
+ chart.updateCustomElements(mockChart, mockOptions);
1624
+
1625
+ expect(chart.getGoalIconPosition).toHaveBeenCalledWith(mockChart, mockOptions);
1626
+ expect(mockChart.goalIcon.attr).toHaveBeenCalledWith(mockGoalIconPosition);
1627
+ });
1628
+
1629
+ it("updates label attributes correctly", () => {
1630
+ chart.updateCustomElements(mockChart, mockOptions);
1631
+
1632
+ expect(chart.getValueLabelPosition).toHaveBeenCalledWith(mockChart, mockOptions);
1633
+ expect(mockChart.label.attr).toHaveBeenCalledWith(mockValueLabelPosition);
1634
+ });
1635
+ });
1636
+
1637
+ describe("moveCustomElementsToFront", () => {
1638
+ let mockChart;
1639
+
1640
+ beforeEach(() => {
1641
+ // Mock chart object with elements having .toFront method
1642
+ mockChart = {
1643
+ startBorder: { toFront: jest.fn() },
1644
+ endBorder: { toFront: jest.fn() },
1645
+ goalIcon: { toFront: jest.fn() },
1646
+ };
1647
+ });
1648
+
1649
+ afterEach(() => {
1650
+ jest.clearAllMocks();
1651
+ });
1652
+
1653
+ it("calls toFront on startBorder", () => {
1654
+ chart.moveCustomElementsToFront(mockChart);
1655
+ expect(mockChart.startBorder.toFront).toHaveBeenCalled();
1656
+ });
1657
+
1658
+ it("calls toFront on endBorder", () => {
1659
+ chart.moveCustomElementsToFront(mockChart);
1660
+ expect(mockChart.endBorder.toFront).toHaveBeenCalled();
1661
+ });
1662
+
1663
+ it("calls toFront on goalIcon", () => {
1664
+ chart.moveCustomElementsToFront(mockChart);
1665
+ expect(mockChart.goalIcon.toFront).toHaveBeenCalled();
1666
+ });
1667
+ });
1668
+
1669
+ describe("clampValueToPane", () => {
1670
+ let mockMax;
1671
+
1672
+ beforeEach(() => {
1673
+ // Mock max value
1674
+ mockMax = 100;
1675
+
1676
+ // Spy on helpers.clamp
1677
+ jest.spyOn(helpers, "clamp").mockImplementation((min, value, max) => {
1678
+ if (value < min) return min;
1679
+ if (value > max) return max;
1680
+ return value;
1681
+ });
1682
+ });
1683
+
1684
+ afterEach(() => {
1685
+ jest.restoreAllMocks();
1686
+ });
1687
+
1688
+ it("calls helpers.clamp with correct bounds and value", () => {
1689
+ const value = 50;
1690
+ const result = chart.clampValueToPane(value, 100);
1691
+
1692
+ expect(helpers.clamp).toHaveBeenCalledWith(-2, value, 102);
1693
+ expect(result).toBe(value); // Value is within range
1694
+ });
1695
+
1696
+ it("returns clamped value when below range", () => {
1697
+ const value = -10;
1698
+ const result = chart.clampValueToPane(value, 100);
1699
+
1700
+ expect(helpers.clamp).toHaveBeenCalledWith(-2, value, 102);
1701
+ expect(result).toBe(-2); // Clamped to minimum
1702
+ });
1703
+
1704
+ it("returns clamped value when above range", () => {
1705
+ const value = 200;
1706
+ const result = chart.clampValueToPane(value, 100);
1707
+
1708
+ expect(helpers.clamp).toHaveBeenCalledWith(-2, value, 102);
1709
+ expect(result).toBe(102); // Clamped to maximum
1710
+ });
1711
+
1712
+ it("uses the deafult max umless it is provided", () => {
1713
+ const value = 50;
1714
+ const result = chart.clampValueToPane(value);
1715
+
1716
+ expect(helpers.clamp).toHaveBeenCalledWith(-3.6, value, 183.6);
1717
+ expect(result).toBe(value); // Value is within range
1718
+ });
1719
+ });
1720
+
1721
+ describe("configChart", () => {
1722
+ const mockChart = {};
1723
+ const mockOptions = {
1724
+ segments: [
1725
+ {
1726
+ from: 0,
1727
+ to: 50,
1728
+ title: "Low",
1729
+ color: "#BF1D30",
1730
+ },
1731
+ {
1732
+ from: 51,
1733
+ to: 160,
1734
+ title: "Medium",
1735
+ color: "#FFA310",
1736
+ },
1737
+ {
1738
+ from: 161,
1739
+ to: 200,
1740
+ title: "High",
1741
+ color: "#037C5A",
1742
+ },
1743
+ ],
1744
+ goal: {
1745
+ name: "Goal",
1746
+ value: 180,
1747
+ },
1748
+ isAbsoluteValue: true,
1749
+ label: {
1750
+ show: true,
1751
+ },
1752
+ gauge: {
1753
+ background: "white",
1754
+ tickLength: 40,
1755
+ tickWidth: 20,
1756
+ pivot: {
1757
+ color: "black",
1758
+ radius: 10,
1759
+ },
1760
+ },
1761
+ };
1762
+
1763
+ beforeEach(() => {
1764
+ chart = new DrGaugeChart(mockPivotData, {
1765
+ chartOptions: mockOptions,
1766
+ });
1767
+ });
1768
+
1769
+ it("returns a configuration object with the correct structure", () => {
1770
+ const config = chart.configChart();
1771
+
1772
+ expect(config).toBeInstanceOf(Object);
1773
+ expect(config).toHaveProperty("title");
1774
+ expect(config).toHaveProperty("subtitle");
1775
+ expect(config).toHaveProperty("chart");
1776
+ expect(config).toHaveProperty("pane");
1777
+ expect(config).toHaveProperty("yAxis");
1778
+ expect(config).toHaveProperty("series");
1779
+ });
1780
+
1781
+ it("hides title", () => {
1782
+ const config = chart.configChart();
1783
+ expect(config.title).toEqual({
1784
+ text: null,
1785
+ });
1786
+ });
1787
+
1788
+ it("hides subtitle", () => {
1789
+ const config = chart.configChart();
1790
+ expect(config.subtitle).toBe(null);
1791
+ });
1792
+
1793
+ it("alllows HTML on export", () => {
1794
+ const config = chart.configChart();
1795
+ expect(config.exporting).toEqual({
1796
+ allowHTML: true,
1797
+ });
1798
+ });
1799
+
1800
+ it("sets chart type", () => {
1801
+ const config = chart.configChart();
1802
+ expect(config.chart.type).toBe("gauge");
1803
+ });
1804
+
1805
+ it("sets default chart options", () => {
1806
+ const config = chart.configChart();
1807
+
1808
+ expect(config.chart.backgroundColor).toBe(mockOptions.gauge.background);
1809
+ expect(config.chart.plotBackgroundColor).toBe(null);
1810
+ expect(config.chart.plotBackgroundImage).toBe(null);
1811
+ expect(config.chart.plotBorderWidth).toBe(0);
1812
+ expect(config.chart.plotShadow).toBe(false);
1813
+ });
1814
+
1815
+ it("removes default offsets", () => {
1816
+ const config = chart.configChart();
1817
+
1818
+ expect(config.chart.margin).toEqual([0, 0, 0, 0]);
1819
+ expect(config.chart.spacing).toEqual([0, 0, 0, 0]);
1820
+ });
1821
+
1822
+ it("sets load event handler", () => {
1823
+ const config = chart.configChart();
1824
+ chart.addTooltips = jest.fn();
1825
+ config.chart.events.load({ target: mockChart });
1826
+ expect(chart.addTooltips).toHaveBeenCalled();
1827
+ });
1828
+
1829
+ it("sets render event handler", () => {
1830
+ const config = chart.configChart();
1831
+ chart.moveCustomElementsToFront = jest.fn();
1832
+ chart.setTicksStyles = jest.fn();
1833
+ config.chart.events.render({ target: mockChart });
1834
+ expect(chart.moveCustomElementsToFront).toHaveBeenCalled();
1835
+ expect(chart.setTicksStyles).toHaveBeenCalled();
1836
+ });
1837
+
1838
+ it("sets beforeRedraw event handler", () => {
1839
+ const config = chart.configChart();
1840
+ chart.setPane = jest.fn();
1841
+ chart.updateCustomElements = jest.fn();
1842
+ config.chart.events.beforeRedraw({ target: mockChart });
1843
+ expect(chart.setPane).toHaveBeenCalled();
1844
+ expect(chart.updateCustomElements).toHaveBeenCalled();
1845
+ });
1846
+
1847
+ it("sets beforeRender event handler", () => {
1848
+ const config = chart.configChart();
1849
+ chart.setPane = jest.fn();
1850
+ chart.setCustomElements = jest.fn();
1851
+ config.chart.events.beforeRender({ target: mockChart });
1852
+ expect(chart.setPane).toHaveBeenCalled();
1853
+ expect(chart.setCustomElements).toHaveBeenCalled();
1854
+ });
1855
+
1856
+ it("sets pane options", () => {
1857
+ const config = chart.configChart();
1858
+
1859
+ expect(config.pane).toEqual({
1860
+ startAngle: -90,
1861
+ endAngle: 90,
1862
+ background: null,
1863
+ center: [0, 0],
1864
+ });
1865
+ });
1866
+
1867
+ it("sets yAxis default options", () => {
1868
+ const config = chart.configChart();
1869
+
1870
+ expect(config.yAxis.min).toBe(0);
1871
+ expect(config.yAxis.max).toBe(200);
1872
+ expect(config.yAxis.tickPositions).toEqual([0, 50, 160, 180, 200]);
1873
+ expect(config.yAxis.tickColor).toBe(mockOptions.gauge.background);
1874
+ expect(config.yAxis.tickLength).toBe(40);
1875
+ expect(config.yAxis.tickWidth).toBe(20);
1876
+ expect(config.yAxis.minorTickInterval).toBe(null);
1877
+ expect(config.yAxis.lineWidth).toBe(0);
1878
+ expect(config.yAxis.plotBands).toEqual([
1879
+ { color: "#BF1D30", from: 0, thickness: 16, title: "Low", to: 50 },
1880
+ { color: "#FFA310", from: 50, thickness: 16, title: "Medium", to: 160 },
1881
+ { color: "#037C5A", from: 160, thickness: 16, title: "High", to: 200 },
1882
+ ]);
1883
+ });
1884
+
1885
+ it("sets yAxis labels options", () => {
1886
+ const config = chart.configChart();
1887
+
1888
+ expect(config.yAxis.labels.enabled).toBe(mockOptions.label.show);
1889
+ expect(config.yAxis.labels.enabled).toBe(mockOptions.label.show);
1890
+ expect(config.yAxis.labels.distance).toBe(0),
1891
+ expect(config.yAxis.labels.verticalAlign).toBe("middle"),
1892
+ expect(config.yAxis.labels.allowOverlap).toBe(true),
1893
+ expect(config.yAxis.labels.align).toBe("left");
1894
+ expect(config.yAxis.labels.style).toEqual({
1895
+ whiteSpace: "nowrap",
1896
+ width: "auto",
1897
+ });
1898
+ expect(config.yAxis.labels.useHTML).toBe(true);
1899
+ chart.formatTickLabel = jest.fn().mockReturnValue("<span>100.00</span>");
1900
+ expect(config.yAxis.labels.formatter({ value: 100 })).toBe("<span>100.00</span>");
1901
+ });
1902
+
1903
+ it("sets series", () => {
1904
+ chart.value = 204;
1905
+ const series = chart.configChart().series[0];
1906
+
1907
+ expect(series.name).toBe(null);
1908
+ expect(series.data).toEqual([204]);
1909
+ expect(series.dataLabels).toEqual([
1910
+ {
1911
+ enabled: false,
1912
+ },
1913
+ ]);
1914
+ expect(series.dial).toEqual({
1915
+ radius: "70%",
1916
+ backgroundColor: mockOptions.gauge.pivot.color,
1917
+ baseWidth: 2 * mockOptions.gauge.pivot.radius,
1918
+ baseLength: "0%",
1919
+ rearLength: "0%",
1920
+ });
1921
+ expect(series.pivot).toEqual({
1922
+ backgroundColor: mockOptions.gauge.pivot.color,
1923
+ radius: mockOptions.gauge.pivot.radius,
1924
+ });
1925
+ });
1926
+ });
1927
+ });
1928
+
1929
+ function simplifyString(str) {
1930
+ return str.replace(/[\s\n\t]/g, "");
1931
+ }