gauge-card.test.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * @vitest-environment happy-dom
  3. */
  4. import { describe, expect, test } from "vitest";
  5. // Test the pure functions extracted from GaugeCard component
  6. // These are the core logic that determines gauge colors and trend indicators
  7. describe("GaugeCard - getGaugeColor logic", () => {
  8. // Replicate the getGaugeColor function logic for testing
  9. function getGaugeColor(
  10. value: number,
  11. thresholds: { warning: number; critical: number },
  12. invertColors: boolean
  13. ): string {
  14. if (invertColors) {
  15. // For metrics where lower is better (error rate, latency)
  16. if (value <= thresholds.critical) return "text-emerald-500";
  17. if (value <= thresholds.warning) return "text-amber-500";
  18. return "text-rose-500";
  19. }
  20. // For metrics where higher is better (availability)
  21. if (value >= thresholds.warning) return "text-emerald-500";
  22. if (value >= thresholds.critical) return "text-amber-500";
  23. return "text-rose-500";
  24. }
  25. describe("normal metrics (higher is better)", () => {
  26. const thresholds = { warning: 80, critical: 50 };
  27. const invertColors = false;
  28. test("should return green for values >= warning threshold", () => {
  29. expect(getGaugeColor(100, thresholds, invertColors)).toBe("text-emerald-500");
  30. expect(getGaugeColor(95, thresholds, invertColors)).toBe("text-emerald-500");
  31. expect(getGaugeColor(80, thresholds, invertColors)).toBe("text-emerald-500");
  32. });
  33. test("should return amber for values between critical and warning", () => {
  34. expect(getGaugeColor(79, thresholds, invertColors)).toBe("text-amber-500");
  35. expect(getGaugeColor(65, thresholds, invertColors)).toBe("text-amber-500");
  36. expect(getGaugeColor(50, thresholds, invertColors)).toBe("text-amber-500");
  37. });
  38. test("should return red for values < critical threshold", () => {
  39. expect(getGaugeColor(49, thresholds, invertColors)).toBe("text-rose-500");
  40. expect(getGaugeColor(25, thresholds, invertColors)).toBe("text-rose-500");
  41. expect(getGaugeColor(0, thresholds, invertColors)).toBe("text-rose-500");
  42. });
  43. });
  44. describe("inverted metrics (lower is better)", () => {
  45. const thresholds = { warning: 10, critical: 5 };
  46. const invertColors = true;
  47. test("should return green for values <= critical threshold", () => {
  48. expect(getGaugeColor(0, thresholds, invertColors)).toBe("text-emerald-500");
  49. expect(getGaugeColor(3, thresholds, invertColors)).toBe("text-emerald-500");
  50. expect(getGaugeColor(5, thresholds, invertColors)).toBe("text-emerald-500");
  51. });
  52. test("should return amber for values between critical and warning", () => {
  53. expect(getGaugeColor(6, thresholds, invertColors)).toBe("text-amber-500");
  54. expect(getGaugeColor(8, thresholds, invertColors)).toBe("text-amber-500");
  55. expect(getGaugeColor(10, thresholds, invertColors)).toBe("text-amber-500");
  56. });
  57. test("should return red for values > warning threshold", () => {
  58. expect(getGaugeColor(11, thresholds, invertColors)).toBe("text-rose-500");
  59. expect(getGaugeColor(50, thresholds, invertColors)).toBe("text-rose-500");
  60. expect(getGaugeColor(100, thresholds, invertColors)).toBe("text-rose-500");
  61. });
  62. });
  63. });
  64. describe("GaugeCard - getTrendColor logic", () => {
  65. function getTrendColor(direction: "up" | "down" | "stable", invertColors: boolean) {
  66. if (direction === "stable") return "text-muted-foreground bg-muted/50";
  67. if (invertColors) {
  68. // For inverted metrics, down is good
  69. return direction === "down"
  70. ? "text-emerald-500 bg-emerald-500/10"
  71. : "text-rose-500 bg-rose-500/10";
  72. }
  73. // For normal metrics, up is good
  74. return direction === "up"
  75. ? "text-emerald-500 bg-emerald-500/10"
  76. : "text-rose-500 bg-rose-500/10";
  77. }
  78. describe("normal metrics", () => {
  79. const invertColors = false;
  80. test("should return green for upward trend", () => {
  81. expect(getTrendColor("up", invertColors)).toBe("text-emerald-500 bg-emerald-500/10");
  82. });
  83. test("should return red for downward trend", () => {
  84. expect(getTrendColor("down", invertColors)).toBe("text-rose-500 bg-rose-500/10");
  85. });
  86. test("should return muted for stable trend", () => {
  87. expect(getTrendColor("stable", invertColors)).toBe("text-muted-foreground bg-muted/50");
  88. });
  89. });
  90. describe("inverted metrics", () => {
  91. const invertColors = true;
  92. test("should return green for downward trend (lower is better)", () => {
  93. expect(getTrendColor("down", invertColors)).toBe("text-emerald-500 bg-emerald-500/10");
  94. });
  95. test("should return red for upward trend (higher is worse)", () => {
  96. expect(getTrendColor("up", invertColors)).toBe("text-rose-500 bg-rose-500/10");
  97. });
  98. test("should return muted for stable trend", () => {
  99. expect(getTrendColor("stable", invertColors)).toBe("text-muted-foreground bg-muted/50");
  100. });
  101. });
  102. });
  103. describe("GaugeCard - SVG calculations", () => {
  104. const sizeConfig = {
  105. sm: { gauge: 64, stroke: 4 },
  106. md: { gauge: 80, stroke: 5 },
  107. lg: { gauge: 96, stroke: 6 },
  108. };
  109. test("should calculate correct radius for each size", () => {
  110. for (const [size, config] of Object.entries(sizeConfig)) {
  111. const radius = (config.gauge - config.stroke) / 2;
  112. expect(radius).toBeGreaterThan(0);
  113. // Radius should be less than half the gauge size
  114. expect(radius).toBeLessThan(config.gauge / 2);
  115. }
  116. });
  117. test("should calculate correct circumference", () => {
  118. const config = sizeConfig.md;
  119. const radius = (config.gauge - config.stroke) / 2;
  120. const circumference = 2 * Math.PI * radius;
  121. expect(circumference).toBeCloseTo(2 * Math.PI * 37.5, 2);
  122. });
  123. test("should calculate correct offset for different values", () => {
  124. const config = sizeConfig.md;
  125. const radius = (config.gauge - config.stroke) / 2;
  126. const circumference = 2 * Math.PI * radius;
  127. // 0% should have full offset (empty gauge)
  128. const offset0 = circumference - (0 / 100) * circumference;
  129. expect(offset0).toBe(circumference);
  130. // 100% should have zero offset (full gauge)
  131. const offset100 = circumference - (100 / 100) * circumference;
  132. expect(offset100).toBe(0);
  133. // 50% should have half offset
  134. const offset50 = circumference - (50 / 100) * circumference;
  135. expect(offset50).toBeCloseTo(circumference / 2, 2);
  136. });
  137. test("should clamp values between 0 and 100", () => {
  138. const normalizeValue = (value: number) => Math.min(Math.max(value, 0), 100);
  139. expect(normalizeValue(-10)).toBe(0);
  140. expect(normalizeValue(0)).toBe(0);
  141. expect(normalizeValue(50)).toBe(50);
  142. expect(normalizeValue(100)).toBe(100);
  143. expect(normalizeValue(150)).toBe(100);
  144. });
  145. });