@datarailsshared/dr_renderer 1.2.452 → 1.2.454

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