@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.
- package/dist/atomix.css +9341 -9249
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -358
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +16 -23
- package/dist/core.js +418 -262
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +11 -18
- package/dist/forms.js +411 -257
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +408 -254
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +33 -40
- package/dist/index.esm.js +663 -453
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +667 -460
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
- package/scripts/atomix-cli.js +34 -1
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +90 -76
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +2 -2
- package/src/components/Callout/README.md +2 -2
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +1 -1
- package/src/components/Form/Select.tsx +1 -1
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +2 -2
- package/src/components/Hero/Hero.tsx +2 -2
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +2 -2
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -4
- package/src/lib/composables/useAtomixGlass.ts +331 -522
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +1 -1
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +13 -21
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.utility-api.scss +6 -6
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
- 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
|
-
|
|
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
|
-
|
|
41
|
+
borderRadius: 999,
|
|
42
42
|
mode: 'shader' as const,
|
|
43
43
|
};
|
|
44
44
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
@@ -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
|
-
|
|
234
|
+
borderRadius: 8,
|
|
235
235
|
mode: 'shader' as const,
|
|
236
236
|
};
|
|
237
237
|
|
|
@@ -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> = ({
|
|
@@ -813,7 +813,7 @@ export const GlassCustom: Story = {
|
|
|
813
813
|
saturation: 180,
|
|
814
814
|
aberrationIntensity: 2.5,
|
|
815
815
|
elasticity: 0.4,
|
|
816
|
-
|
|
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
|
-
|
|
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
|
+
});
|