@shohojdhara/atomix 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/atomix.css +9341 -9249
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +12 -19
  6. package/dist/charts.js +555 -358
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +16 -23
  9. package/dist/core.js +418 -262
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +11 -18
  12. package/dist/forms.js +411 -257
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +14 -21
  15. package/dist/heavy.js +408 -254
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +33 -40
  18. package/dist/index.esm.js +663 -453
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +667 -460
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +1 -1
  25. package/scripts/atomix-cli.js +34 -1
  26. package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
  27. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
  28. package/src/components/AtomixGlass/README.md +5 -5
  29. package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
  31. package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
  32. package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
  33. package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
  34. package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
  35. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
  36. package/src/components/Badge/Badge.stories.tsx +1 -1
  37. package/src/components/Badge/Badge.tsx +1 -1
  38. package/src/components/Breadcrumb/Breadcrumb.tsx +90 -76
  39. package/src/components/Breadcrumb/index.ts +2 -2
  40. package/src/components/Button/Button.stories.tsx +1 -1
  41. package/src/components/Button/README.md +2 -2
  42. package/src/components/Callout/Callout.test.tsx +3 -3
  43. package/src/components/Callout/Callout.tsx +2 -2
  44. package/src/components/Callout/README.md +2 -2
  45. package/src/components/Chart/Chart.stories.tsx +1 -1
  46. package/src/components/Chart/Chart.tsx +5 -5
  47. package/src/components/Chart/TreemapChart.tsx +37 -29
  48. package/src/components/DatePicker/readme.md +3 -3
  49. package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
  50. package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
  51. package/src/components/Form/Checkbox.stories.tsx +1 -1
  52. package/src/components/Form/Checkbox.tsx +1 -1
  53. package/src/components/Form/Input.stories.tsx +1 -1
  54. package/src/components/Form/Input.tsx +1 -1
  55. package/src/components/Form/Radio.stories.tsx +1 -1
  56. package/src/components/Form/Radio.tsx +1 -1
  57. package/src/components/Form/Select.stories.tsx +1 -1
  58. package/src/components/Form/Select.tsx +1 -1
  59. package/src/components/Form/Textarea.stories.tsx +1 -1
  60. package/src/components/Form/Textarea.tsx +1 -1
  61. package/src/components/Hero/Hero.stories.tsx +2 -2
  62. package/src/components/Hero/Hero.tsx +2 -2
  63. package/src/components/Messages/Messages.stories.tsx +1 -1
  64. package/src/components/Messages/Messages.tsx +2 -2
  65. package/src/components/Modal/Modal.stories.tsx +1 -1
  66. package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
  67. package/src/components/Navigation/Nav/Nav.tsx +1 -1
  68. package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
  69. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  70. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
  71. package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
  72. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  73. package/src/components/Pagination/Pagination.tsx +1 -1
  74. package/src/components/Popover/Popover.stories.tsx +1 -1
  75. package/src/components/Popover/Popover.tsx +1 -1
  76. package/src/components/Progress/Progress.tsx +1 -1
  77. package/src/components/Rating/Rating.stories.tsx +1 -1
  78. package/src/components/Rating/Rating.test.tsx +73 -0
  79. package/src/components/Rating/Rating.tsx +25 -37
  80. package/src/components/Spinner/Spinner.tsx +1 -1
  81. package/src/components/Steps/Steps.stories.tsx +1 -1
  82. package/src/components/Steps/Steps.tsx +2 -2
  83. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  84. package/src/components/Tabs/Tabs.tsx +1 -1
  85. package/src/components/Todo/Todo.tsx +0 -1
  86. package/src/components/Toggle/Toggle.stories.tsx +1 -1
  87. package/src/components/Toggle/Toggle.tsx +1 -1
  88. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  89. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
  90. package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
  91. package/src/lib/composables/__tests__/useChart.test.ts +50 -0
  92. package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
  93. package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
  94. package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
  95. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
  96. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
  97. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
  98. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
  99. package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
  100. package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
  101. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
  102. package/src/lib/composables/glass-styles.ts +302 -0
  103. package/src/lib/composables/index.ts +0 -4
  104. package/src/lib/composables/useAtomixGlass.ts +331 -522
  105. package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
  106. package/src/lib/composables/useBarChart.ts +1 -1
  107. package/src/lib/composables/useBreadcrumb.ts +6 -6
  108. package/src/lib/composables/useChart.ts +104 -21
  109. package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
  110. package/src/lib/composables/useSlider.ts +66 -34
  111. package/src/lib/theme/devtools/CLI.ts +1 -1
  112. package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
  113. package/src/lib/types/components.ts +13 -21
  114. package/src/lib/utils/__tests__/dom.test.ts +100 -0
  115. package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
  116. package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
  117. package/src/styles/02-tools/_tools.utility-api.scss +6 -6
  118. package/src/styles/06-components/_components.accordion.scss +0 -2
  119. package/src/styles/06-components/_components.chart.scss +0 -1
  120. package/src/styles/06-components/_components.dropdown.scss +0 -1
  121. package/src/styles/06-components/_components.edge-panel.scss +0 -2
  122. package/src/styles/06-components/_components.photoviewer.scss +0 -1
  123. package/src/styles/06-components/_components.river.scss +0 -1
  124. package/src/styles/06-components/_components.slider.scss +0 -3
  125. package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
  126. package/src/styles/99-utilities/_utilities.text.scss +1 -0
@@ -5,6 +5,27 @@ import type { RatingProps } from '../../lib/types/components';
5
5
  import useForkRef from '../../lib/utils/useForkRef';
6
6
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
7
7
 
8
+ // Helper function to calculate star value based on mouse position
9
+ const calculateStarValue = (
10
+ e: React.MouseEvent,
11
+ starValue: number,
12
+ allowHalf: boolean
13
+ ): number => {
14
+ if (!allowHalf) {
15
+ return starValue;
16
+ }
17
+
18
+ // Get the star element's bounding rectangle
19
+ const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
20
+ // Calculate the x position within the star
21
+ const starCenterX = starRect.left + starRect.width / 2;
22
+ // If mouse is on the left half of the star, use half value
23
+ const isHalfStar = e.clientX < starCenterX;
24
+ const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
25
+
26
+ return Math.max(0.5, adjustedValue); // Ensure minimum of 0.5
27
+ };
28
+
8
29
  /**
9
30
  * Rating component for displaying and collecting star ratings
10
31
  *
@@ -61,19 +82,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
61
82
  const handleMouseEnter = useCallback(
62
83
  (e: React.MouseEvent, starValue: number) => {
63
84
  if (readOnly) return;
64
-
65
- if (allowHalf) {
66
- // Get the star element's bounding rectangle
67
- const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
68
- // Calculate the x position within the star
69
- const starCenterX = starRect.left + starRect.width / 2;
70
- // If mouse is on the left half of the star, use half value
71
- const isHalfStar = e.clientX < starCenterX;
72
- const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
73
- setHoverValue(Math.max(0.5, adjustedValue)); // Ensure minimum of 0.5
74
- } else {
75
- setHoverValue(starValue);
76
- }
85
+ setHoverValue(calculateStarValue(e, starValue, !!allowHalf));
77
86
  },
78
87
  [readOnly, allowHalf, setHoverValue]
79
88
  );
@@ -82,15 +91,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
82
91
  const handleMouseMove = useCallback(
83
92
  (e: React.MouseEvent, starValue: number) => {
84
93
  if (readOnly || !allowHalf) return;
85
-
86
- // Get the star element's bounding rectangle
87
- const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
88
- // Calculate the x position within the star
89
- const starCenterX = starRect.left + starRect.width / 2;
90
- // If mouse is on the left half of the star, use half value
91
- const isHalfStar = e.clientX < starCenterX;
92
- const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
93
- setHoverValue(Math.max(0.5, adjustedValue)); // Ensure minimum of 0.5
94
+ setHoverValue(calculateStarValue(e, starValue, !!allowHalf));
94
95
  },
95
96
  [readOnly, allowHalf, setHoverValue]
96
97
  );
@@ -105,20 +106,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
105
106
  const handleClick = useCallback(
106
107
  (e: React.MouseEvent, starValue: number) => {
107
108
  if (readOnly) return;
108
-
109
- let newValue = starValue;
110
-
111
- if (allowHalf) {
112
- // Get the star element's bounding rectangle
113
- const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
114
- // Calculate the x position within the star
115
- const starCenterX = starRect.left + starRect.width / 2;
116
- // If click is on the left half of the star, use half value
117
- const isHalfStar = e.clientX < starCenterX;
118
- newValue = isHalfStar ? starValue - 0.5 : starValue;
119
- newValue = Math.max(0.5, newValue); // Ensure minimum of 0.5
120
- }
121
-
109
+ const newValue = calculateStarValue(e, starValue, !!allowHalf);
122
110
  onChange?.(newValue);
123
111
  },
124
112
  [readOnly, onChange, allowHalf]
@@ -291,7 +279,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
291
279
  blurAmount: 1,
292
280
  saturation: 160,
293
281
  aberrationIntensity: 0.5,
294
- cornerRadius: 8,
282
+ borderRadius: 8,
295
283
  mode: 'shader' as const,
296
284
  };
297
285
 
@@ -38,7 +38,7 @@ export const Spinner: React.FC<SpinnerProps> = memo(
38
38
  const defaultGlassProps = {
39
39
  displacementScale: 20,
40
40
  blurAmount: 1,
41
- cornerRadius: 999,
41
+ borderRadius: 999,
42
42
  mode: 'shader' as const,
43
43
  };
44
44
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
@@ -404,7 +404,7 @@ export const GlassCustom: Story = {
404
404
  blurAmount: 2,
405
405
  saturation: 200,
406
406
  aberrationIntensity: 0.8,
407
- cornerRadius: 12,
407
+ borderRadius: 12,
408
408
  },
409
409
  },
410
410
  render: args => (
@@ -24,7 +24,7 @@ export interface StepItemData {
24
24
  export type { StepItemData as StepItem };
25
25
 
26
26
  // Compound Component Props
27
- export interface StepsItemProps extends React.HTMLAttributes<HTMLDivElement> {
27
+ export interface StepsItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
28
28
  /**
29
29
  * The number or icon for the step
30
30
  */
@@ -231,7 +231,7 @@ const StepsComp: React.FC<StepsProps> = ({
231
231
  blurAmount: 1,
232
232
  saturation: 160,
233
233
  aberrationIntensity: 0.5,
234
- cornerRadius: 8,
234
+ borderRadius: 8,
235
235
  mode: 'shader' as const,
236
236
  };
237
237
 
@@ -372,7 +372,7 @@ export const GlassCustom: Story = {
372
372
  blurAmount: 2,
373
373
  saturation: 200,
374
374
  aberrationIntensity: 0.8,
375
- cornerRadius: 12,
375
+ borderRadius: 12,
376
376
  } as GlassProps,
377
377
  },
378
378
  render: args => (
@@ -277,7 +277,7 @@ export const Tabs: TabsComponent = memo(
277
277
  blurAmount: 1,
278
278
  saturation: 160,
279
279
  aberrationIntensity: 0.5,
280
- cornerRadius: 8,
280
+ borderRadius: 8,
281
281
  mode: 'shader' as const,
282
282
  };
283
283
 
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
2
2
  import { TodoProps } from '../../lib/types/components';
3
3
  import { useTodo } from '../../lib/composables/useTodo';
4
4
  import { Icon } from '../Icon/Icon';
5
- import { TODO } from '../../lib/constants/components';
6
5
  import { generateUUID } from '../../lib/utils';
7
6
 
8
7
  export const Todo: React.FC<TodoProps> = ({
@@ -266,7 +266,7 @@ export const GlassCustom: Story = {
266
266
  blurAmount: 2,
267
267
  saturation: 200,
268
268
  aberrationIntensity: 0.8,
269
- cornerRadius: 12,
269
+ borderRadius: 12,
270
270
  children: <div>Custom glass</div>,
271
271
  },
272
272
  },
@@ -121,7 +121,7 @@ export const Toggle: React.FC<ToggleProps> = ({
121
121
  blurAmount: 1,
122
122
  saturation: 160,
123
123
  aberrationIntensity: 0.5,
124
- cornerRadius: 8,
124
+ borderRadius: 8,
125
125
  mode: 'shader' as const,
126
126
  };
127
127
 
@@ -349,7 +349,7 @@ export const GlassTooltipCustom: Story = {
349
349
  blurAmount: 2,
350
350
  saturation: 200,
351
351
  aberrationIntensity: 1,
352
- cornerRadius: 12,
352
+ borderRadius: 12,
353
353
  mode: 'polar',
354
354
  } as GlassProps,
355
355
  } as any,
@@ -813,7 +813,7 @@ export const GlassCustom: Story = {
813
813
  saturation: 180,
814
814
  aberrationIntensity: 2.5,
815
815
  elasticity: 0.4,
816
- cornerRadius: 20,
816
+ borderRadius: 20,
817
817
  mode: 'prominent',
818
818
  overLight: false,
819
819
  },
@@ -1071,7 +1071,7 @@ export const GlassWithInteractiveContent: Story = {
1071
1071
  saturation: 170,
1072
1072
  aberrationIntensity: 2,
1073
1073
  elasticity: 0.3,
1074
- cornerRadius: 15,
1074
+ borderRadius: 15,
1075
1075
  mode: 'standard',
1076
1076
  },
1077
1077
  glassOpacity: 0.6,
@@ -0,0 +1,88 @@
1
+ import React, { useRef } from 'react';
2
+ import { render, act } from '@testing-library/react';
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
4
+ import { useAtomixGlass } from '../useAtomixGlass';
5
+
6
+ describe('useAtomixGlass Performance', () => {
7
+ let renderCount = 0;
8
+ let glassRefOut: React.RefObject<HTMLDivElement> | null = null;
9
+
10
+ const TestComponent = () => {
11
+ renderCount++;
12
+ const glassRef = useRef<HTMLDivElement>(null);
13
+ const contentRef = useRef<HTMLDivElement>(null);
14
+ glassRefOut = glassRef;
15
+
16
+ useAtomixGlass({
17
+ glassRef,
18
+ contentRef,
19
+ elasticity: 0.1, // Ensure elasticity so transform changes
20
+ });
21
+
22
+ return (
23
+ <div ref={glassRef} style={{ width: 200, height: 100 }}>
24
+ <div ref={contentRef}>Content</div>
25
+ </div>
26
+ );
27
+ };
28
+
29
+ beforeEach(() => {
30
+ renderCount = 0;
31
+ glassRefOut = null;
32
+ // Mock getBoundingClientRect
33
+ Element.prototype.getBoundingClientRect = vi.fn(() => ({
34
+ width: 200,
35
+ height: 100,
36
+ top: 0,
37
+ left: 0,
38
+ bottom: 100,
39
+ right: 200,
40
+ x: 0,
41
+ y: 0,
42
+ toJSON: () => {},
43
+ }));
44
+ vi.useFakeTimers();
45
+ });
46
+
47
+ afterEach(() => {
48
+ vi.useRealTimers();
49
+ vi.restoreAllMocks();
50
+ });
51
+
52
+ it('does not re-render on mouse move but updates styles', async () => {
53
+ render(<TestComponent />);
54
+
55
+ // Run initial effects
56
+ await act(async () => {
57
+ vi.runAllTimers();
58
+ });
59
+
60
+ const countAfterSetup = renderCount;
61
+ const initialTransform = glassRefOut?.current?.style.transform;
62
+ console.log(`Initial transform: ${initialTransform}`);
63
+
64
+ // Simulate mouse move
65
+ const moveEvent = new MouseEvent('mousemove', {
66
+ clientX: 50,
67
+ clientY: 50,
68
+ bubbles: true,
69
+ });
70
+
71
+ await act(async () => {
72
+ document.dispatchEvent(moveEvent);
73
+ vi.runAllTimers();
74
+ });
75
+
76
+ console.log(`Render count: ${renderCount}`);
77
+
78
+ // Expect NO re-render
79
+ expect(renderCount).toBe(countAfterSetup);
80
+
81
+ // Expect style update
82
+ const finalTransform = glassRefOut?.current?.style.transform;
83
+ console.log(`Final transform: ${finalTransform}`);
84
+
85
+ expect(finalTransform).not.toBe(initialTransform);
86
+ expect(finalTransform).toContain('translate');
87
+ });
88
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getDatasetBounds } from '../useChart';
3
+ import { ChartDataPoint } from '../../types/components';
4
+
5
+ describe('useChart', () => {
6
+ describe('getDatasetBounds', () => {
7
+ it('should correctly calculate min and max for valid numeric data', () => {
8
+ const data: ChartDataPoint[] = [
9
+ { label: 'A', value: 10 },
10
+ { label: 'B', value: 5 },
11
+ { label: 'C', value: 20 },
12
+ ];
13
+ const result = getDatasetBounds(data);
14
+ expect(result).toEqual({ min: 5, max: 20, hasValid: true });
15
+ });
16
+
17
+ it('should handle empty data', () => {
18
+ const data: ChartDataPoint[] = [];
19
+ const result = getDatasetBounds(data);
20
+ expect(result).toEqual({ min: Infinity, max: -Infinity, hasValid: false });
21
+ });
22
+
23
+ it('should handle undefined data', () => {
24
+ const result = getDatasetBounds(undefined);
25
+ expect(result).toEqual({ min: Infinity, max: -Infinity, hasValid: false });
26
+ });
27
+
28
+ it('should ignore invalid values', () => {
29
+ const data: any[] = [
30
+ { label: 'A', value: 10 },
31
+ { label: 'B', value: 'invalid' },
32
+ { label: 'C', value: null },
33
+ { label: 'D', value: 30 },
34
+ ];
35
+ const result = getDatasetBounds(data as ChartDataPoint[]);
36
+ expect(result).toEqual({ min: 10, max: 30, hasValid: true });
37
+ });
38
+
39
+ it('should handle large datasets without stack overflow', () => {
40
+ const size = 150000;
41
+ const data: ChartDataPoint[] = Array.from({ length: size }, (_, i) => ({
42
+ label: `Point ${i}`,
43
+ value: i
44
+ }));
45
+
46
+ const result = getDatasetBounds(data);
47
+ expect(result).toEqual({ min: 0, max: size - 1, hasValid: true });
48
+ });
49
+ });
50
+ });
@@ -0,0 +1,139 @@
1
+ import { renderHook, act } from '@testing-library/react';
2
+ import { useChartData } from '../useChart';
3
+ import { ChartDataset } from '../../types/components';
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
5
+
6
+ describe('useChartData - Real-time Optimization', () => {
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.useRealTimers();
13
+ });
14
+
15
+ it('should not update processedData if data has not changed', () => {
16
+ const datasets: ChartDataset[] = [
17
+ {
18
+ label: 'Dataset 1',
19
+ data: [
20
+ { label: '1', value: 10 },
21
+ { label: '2', value: 20 },
22
+ ],
23
+ },
24
+ ];
25
+
26
+ const { result } = renderHook(() =>
27
+ useChartData(datasets, {
28
+ enableRealTime: true,
29
+ realTimeInterval: 1000,
30
+ enableDecimation: false,
31
+ })
32
+ );
33
+
34
+ const initialData = result.current.processedData;
35
+
36
+ // Advance time
37
+ act(() => {
38
+ vi.advanceTimersByTime(1100);
39
+ });
40
+
41
+ expect(result.current.processedData).toBe(initialData);
42
+ });
43
+
44
+ it('should update processedData if data length changes (mutation)', () => {
45
+ const datasets: ChartDataset[] = [
46
+ {
47
+ label: 'Dataset 1',
48
+ data: [
49
+ { label: '1', value: 10 },
50
+ { label: '2', value: 20 },
51
+ ],
52
+ },
53
+ ];
54
+
55
+ const { result } = renderHook(() =>
56
+ useChartData(datasets, {
57
+ enableRealTime: true,
58
+ realTimeInterval: 1000,
59
+ enableDecimation: false,
60
+ })
61
+ );
62
+
63
+ const initialData = result.current.processedData;
64
+
65
+ datasets[0].data.push({ label: '3', value: 30 });
66
+
67
+ act(() => {
68
+ vi.advanceTimersByTime(1100);
69
+ });
70
+
71
+ expect(result.current.processedData).not.toBe(initialData);
72
+ expect(result.current.processedData[0].data.length).toBe(3);
73
+ });
74
+
75
+ it('should update processedData if data value changes (mutation)', () => {
76
+ const datasets: ChartDataset[] = [
77
+ {
78
+ label: 'Dataset 1',
79
+ data: [
80
+ { label: '1', value: 10 },
81
+ { label: '2', value: 20 },
82
+ ],
83
+ },
84
+ ];
85
+
86
+ const { result } = renderHook(() =>
87
+ useChartData(datasets, {
88
+ enableRealTime: true,
89
+ realTimeInterval: 1000,
90
+ enableDecimation: false,
91
+ })
92
+ );
93
+
94
+ const initialData = result.current.processedData;
95
+
96
+ datasets[0].data[1].value = 25;
97
+
98
+ act(() => {
99
+ vi.advanceTimersByTime(1100);
100
+ });
101
+
102
+ expect(result.current.processedData).not.toBe(initialData);
103
+ expect(result.current.processedData[0].data[1].value).toBe(25);
104
+ });
105
+
106
+ it('should update processedData if historical data changes (mutation)', () => {
107
+ const datasets: ChartDataset[] = [
108
+ {
109
+ label: 'Dataset 1',
110
+ data: [
111
+ { label: '1', value: 10 },
112
+ { label: '2', value: 20 },
113
+ { label: '3', value: 30 },
114
+ ],
115
+ },
116
+ ];
117
+
118
+ const { result } = renderHook(() =>
119
+ useChartData(datasets, {
120
+ enableRealTime: true,
121
+ realTimeInterval: 1000,
122
+ enableDecimation: false,
123
+ })
124
+ );
125
+
126
+ const initialData = result.current.processedData;
127
+
128
+ // Mutate historical data (index 0)
129
+ datasets[0].data[0].value = 15;
130
+
131
+ act(() => {
132
+ vi.advanceTimersByTime(1100);
133
+ });
134
+
135
+ // Should update
136
+ expect(result.current.processedData).not.toBe(initialData);
137
+ expect(result.current.processedData[0].data[0].value).toBe(15);
138
+ });
139
+ });
@@ -0,0 +1,59 @@
1
+ import { renderHook, act } from '@testing-library/react';
2
+ import { useHeroBackgroundSlider } from '../useHeroBackgroundSlider';
3
+ import { HeroBackgroundSliderConfig } from '../../types/components';
4
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+
6
+ describe('useHeroBackgroundSlider Performance', () => {
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.useRealTimers();
13
+ vi.restoreAllMocks();
14
+ });
15
+
16
+ it('should not reset interval frequently during autoplay', () => {
17
+ const config: HeroBackgroundSliderConfig = {
18
+ slides: [
19
+ { type: 'image', src: 'slide1.jpg' },
20
+ { type: 'image', src: 'slide2.jpg' },
21
+ { type: 'image', src: 'slide3.jpg' },
22
+ ],
23
+ autoplay: {
24
+ delay: 3000,
25
+ },
26
+ transitionDuration: 1000,
27
+ };
28
+
29
+ const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
30
+
31
+ const { result } = renderHook(() => useHeroBackgroundSlider(config));
32
+
33
+ // Reset call count after initial render
34
+ clearIntervalSpy.mockClear();
35
+
36
+ // 1st Transition
37
+ act(() => {
38
+ vi.advanceTimersByTime(3000);
39
+ });
40
+
41
+ act(() => {
42
+ vi.advanceTimersByTime(1000);
43
+ });
44
+
45
+ // 2nd Transition
46
+ act(() => {
47
+ vi.advanceTimersByTime(3000);
48
+ });
49
+
50
+ act(() => {
51
+ vi.advanceTimersByTime(1000);
52
+ });
53
+
54
+ console.log(`clearInterval calls: ${clearIntervalSpy.mock.calls.length}`);
55
+
56
+ // With optimization, the interval should persist and not be cleared during transitions
57
+ expect(clearIntervalSpy).toHaveBeenCalledTimes(0);
58
+ });
59
+ });
@@ -0,0 +1,68 @@
1
+ import { renderHook, act } from '@testing-library/react';
2
+ import { useSlider } from '../useSlider';
3
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
+ import { SliderSlide } from '../../types/components';
5
+
6
+ describe('useSlider Autoplay Optimization', () => {
7
+ const slides: SliderSlide[] = [
8
+ { id: '1', content: 'Slide 1' },
9
+ { id: '2', content: 'Slide 2' },
10
+ { id: '3', content: 'Slide 3' },
11
+ ];
12
+
13
+ beforeEach(() => {
14
+ vi.useFakeTimers();
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.useRealTimers();
19
+ vi.restoreAllMocks();
20
+ });
21
+
22
+ it('should not reset interval when transitioning state changes', () => {
23
+ const setIntervalSpy = vi.spyOn(global, 'setInterval');
24
+ const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
25
+
26
+ const autoplayConfig = { delay: 1000 };
27
+
28
+ const { result } = renderHook(() =>
29
+ useSlider({
30
+ slides,
31
+ autoplay: autoplayConfig,
32
+ speed: 300,
33
+ slidesToShow: 1,
34
+ })
35
+ );
36
+
37
+ // Initial render should set interval.
38
+ // Note: It might be called more than once due to initial state updates (like internalIndex setting)
39
+ // causing re-renders if dependencies are unstable, though refs should be stable.
40
+ // However, strictly, we want to verify it doesn't reset DURING autoplay cycle.
41
+
42
+ // Let's clear mocks after initial render is done.
43
+ setIntervalSpy.mockClear();
44
+ clearIntervalSpy.mockClear();
45
+
46
+ // Fast-forward to trigger autoplay
47
+ act(() => {
48
+ vi.advanceTimersByTime(1000);
49
+ });
50
+
51
+ // Check if transition started
52
+ expect(result.current.transitioning).toBe(true);
53
+
54
+ // At this point, in the buggy version, transitioning becoming true triggers the effect cleanup and re-setup.
55
+ // So we expect setInterval/clearInterval to have been called.
56
+
57
+ // Let's see what happens after transition ends
58
+ act(() => {
59
+ vi.advanceTimersByTime(300); // speed is 300
60
+ });
61
+
62
+ expect(result.current.transitioning).toBe(false);
63
+
64
+ // In the optimized version, these should be 0 because the interval persists.
65
+ expect(setIntervalSpy).toHaveBeenCalledTimes(0);
66
+ expect(clearIntervalSpy).toHaveBeenCalledTimes(0);
67
+ });
68
+ });