@bug-on/md3-react 2.0.3 → 3.0.0
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/.turbo/turbo-build.log +33 -0
- package/CHANGELOG.md +55 -0
- package/dist/index.css.d.ts +2 -0
- package/dist/index.d.mts +6127 -0
- package/dist/index.d.ts +6127 -71
- package/dist/index.js +1653 -614
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1566 -547
- package/dist/index.mjs.map +1 -1
- package/dist/material-symbols-cdn.css.d.ts +2 -0
- package/dist/material-symbols-self-hosted.css.d.ts +2 -0
- package/dist/typography.css.d.ts +2 -0
- package/package.json +22 -19
- package/scripts/copy-assets.js +82 -0
- package/src/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
- package/src/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/loading-indicator.svg +19 -0
- package/src/assets/material-symbols-cdn.css +65 -0
- package/src/assets/material-symbols-self-hosted.css +90 -0
- package/src/css.d.ts +20 -0
- package/src/hooks/useClickOutside.ts +37 -0
- package/src/hooks/useMediaQuery.ts +28 -0
- package/src/hooks/useRipple.ts +88 -0
- package/src/index.css +23 -0
- package/src/index.ts +349 -0
- package/src/lib/material-symbols-preconnect.tsx +82 -0
- package/src/lib/theme-utils.ts +180 -0
- package/src/lib/utils.ts +6 -0
- package/src/test/button.test.tsx +59 -0
- package/src/test/icon.test.tsx +91 -0
- package/src/test/loading-indicator.test.tsx +128 -0
- package/src/test/progress-indicator.test.tsx +306 -0
- package/src/test/setup.ts +80 -0
- package/src/test/typography.test.tsx +206 -0
- package/src/types/index.ts +7 -0
- package/src/types/md3.ts +31 -0
- package/src/ui/Text.tsx +60 -0
- package/src/ui/__snapshots__/divider.test.tsx.snap +63 -0
- package/src/ui/app-bar/app-bar-column.tsx +99 -0
- package/src/ui/app-bar/app-bar-item-button.tsx +71 -0
- package/src/ui/app-bar/app-bar-items.test.tsx +89 -0
- package/src/ui/app-bar/app-bar-overflow-indicator.tsx +108 -0
- package/src/ui/app-bar/app-bar-row.tsx +104 -0
- package/src/ui/app-bar/app-bar.test.tsx +87 -0
- package/src/ui/app-bar/app-bar.tokens.ts +223 -0
- package/src/ui/app-bar/app-bar.types.ts +441 -0
- package/src/ui/app-bar/bottom-app-bar.test.tsx +42 -0
- package/src/ui/app-bar/bottom-app-bar.tsx +84 -0
- package/src/ui/app-bar/docked-toolbar.test.tsx +34 -0
- package/src/ui/app-bar/docked-toolbar.tsx +54 -0
- package/src/ui/app-bar/flexible-app-bar.test.tsx +75 -0
- package/src/ui/app-bar/hooks/use-app-bar-scroll.ts +110 -0
- package/src/ui/app-bar/hooks/use-flexible-app-bar.ts +123 -0
- package/{dist/ui/app-bar/index.d.ts → src/ui/app-bar/index.ts} +35 -2
- package/src/ui/app-bar/large-flexible-app-bar.tsx +165 -0
- package/src/ui/app-bar/medium-flexible-app-bar.tsx +167 -0
- package/src/ui/app-bar/search-app-bar.test.tsx +49 -0
- package/src/ui/app-bar/search-app-bar.tsx +176 -0
- package/src/ui/app-bar/search-view.tsx +227 -0
- package/src/ui/app-bar/small-app-bar.test.tsx +48 -0
- package/src/ui/app-bar/small-app-bar.tsx +203 -0
- package/src/ui/badge.test.tsx +345 -0
- package/src/ui/badge.tsx +282 -0
- package/src/ui/button-group.test.tsx +71 -0
- package/src/ui/button-group.tsx +350 -0
- package/src/ui/button.test.tsx +297 -0
- package/src/ui/button.tsx +669 -0
- package/src/ui/card.test.tsx +187 -0
- package/src/ui/card.tsx +259 -0
- package/src/ui/checkbox.test.tsx +423 -0
- package/src/ui/checkbox.tsx +525 -0
- package/src/ui/chip.test.tsx +292 -0
- package/src/ui/chip.tsx +548 -0
- package/src/ui/code-block.tsx +219 -0
- package/src/ui/dialog.test.tsx +300 -0
- package/src/ui/dialog.tsx +384 -0
- package/src/ui/divider.test.tsx +314 -0
- package/src/ui/divider.tsx +412 -0
- package/src/ui/drawer.tsx +240 -0
- package/src/ui/fab-menu.test.tsx +494 -0
- package/src/ui/fab-menu.tsx +739 -0
- package/src/ui/fab.test.tsx +232 -0
- package/src/ui/fab.tsx +505 -0
- package/src/ui/icon-button.test.tsx +515 -0
- package/src/ui/icon-button.tsx +525 -0
- package/src/ui/icon.test.tsx +197 -0
- package/src/ui/icon.tsx +179 -0
- package/src/ui/loading-indicator.test.tsx +73 -0
- package/src/ui/loading-indicator.tsx +312 -0
- package/src/ui/menu/context-menu.tsx +275 -0
- package/src/ui/menu/index.ts +77 -0
- package/src/ui/menu/menu-animations.ts +102 -0
- package/src/ui/menu/menu-context.tsx +99 -0
- package/src/ui/menu/menu-divider.tsx +47 -0
- package/src/ui/menu/menu-group.tsx +200 -0
- package/src/ui/menu/menu-item.tsx +294 -0
- package/src/ui/menu/menu-tokens.ts +208 -0
- package/src/ui/menu/menu-types.ts +313 -0
- package/src/ui/menu/menu.test.tsx +624 -0
- package/src/ui/menu/menu.tsx +289 -0
- package/src/ui/menu/sub-menu.tsx +223 -0
- package/src/ui/menu/vertical-menu.tsx +382 -0
- package/src/ui/navigation-rail.test.tsx +404 -0
- package/src/ui/navigation-rail.tsx +604 -0
- package/src/ui/progress-indicator/circular.tsx +248 -0
- package/src/ui/progress-indicator/hooks.ts +51 -0
- package/{dist/ui/progress-indicator/index.d.ts → src/ui/progress-indicator/index.tsx} +20 -2
- package/src/ui/progress-indicator/linear-flat.tsx +83 -0
- package/src/ui/progress-indicator/linear-wavy.tsx +243 -0
- package/src/ui/progress-indicator/linear.tsx +143 -0
- package/src/ui/progress-indicator/types.ts +158 -0
- package/src/ui/progress-indicator/utils.ts +73 -0
- package/src/ui/radio-button.test.tsx +407 -0
- package/src/ui/radio-button.tsx +551 -0
- package/src/ui/ripple.test.tsx +72 -0
- package/src/ui/ripple.tsx +234 -0
- package/src/ui/scroll-area.test.tsx +58 -0
- package/src/ui/scroll-area.tsx +139 -0
- package/src/ui/search/animated-placeholder.tsx +145 -0
- package/src/ui/search/hooks/use-search-keyboard.test.ts +202 -0
- package/src/ui/search/hooks/use-search-keyboard.ts +104 -0
- package/src/ui/search/hooks/use-search-view-focus.test.ts +96 -0
- package/src/ui/search/hooks/use-search-view-focus.ts +24 -0
- package/src/ui/search/index.ts +44 -0
- package/src/ui/search/search-bar.tsx +220 -0
- package/src/ui/search/search-context.tsx +42 -0
- package/src/ui/search/search-view-docked.tsx +194 -0
- package/src/ui/search/search-view-fullscreen.tsx +247 -0
- package/src/ui/search/search.test.tsx +233 -0
- package/src/ui/search/search.tokens.ts +134 -0
- package/src/ui/search/search.tsx +131 -0
- package/src/ui/search/search.types.ts +154 -0
- package/src/ui/search/trailing-action.tsx +49 -0
- package/src/ui/shared/constants.ts +122 -0
- package/{dist/ui/shared/touch-target.d.ts → src/ui/shared/touch-target.tsx} +13 -1
- package/src/ui/slider/hooks/useSliderMath.ts +195 -0
- package/{dist/ui/slider/index.d.ts → src/ui/slider/index.ts} +12 -1
- package/src/ui/slider/range-slider.tsx +561 -0
- package/src/ui/slider/slider-thumb.tsx +379 -0
- package/src/ui/slider/slider-track.tsx +912 -0
- package/src/ui/slider/slider.tokens.ts +189 -0
- package/src/ui/slider/slider.tsx +259 -0
- package/src/ui/slider/slider.types.ts +288 -0
- package/src/ui/snackbar/index.ts +20 -0
- package/src/ui/snackbar/snackbar.test.tsx +338 -0
- package/src/ui/snackbar/snackbar.tsx +476 -0
- package/{dist/ui/switch/index.d.ts → src/ui/switch/index.ts} +1 -0
- package/src/ui/switch/switch.stories.tsx +309 -0
- package/src/ui/switch/switch.test.tsx +243 -0
- package/src/ui/switch/switch.tokens.ts +89 -0
- package/src/ui/switch/switch.tsx +504 -0
- package/src/ui/switch/switch.types.ts +62 -0
- package/{dist/ui/tabs/index.d.ts → src/ui/tabs/index.ts} +8 -1
- package/src/ui/tabs/tab.tsx +407 -0
- package/src/ui/tabs/tabs-content.tsx +89 -0
- package/src/ui/tabs/tabs-list.tsx +146 -0
- package/src/ui/tabs/tabs.test.tsx +290 -0
- package/src/ui/tabs/tabs.tokens.ts +121 -0
- package/src/ui/tabs/tabs.tsx +229 -0
- package/src/ui/tabs/tabs.types.ts +185 -0
- package/{dist/ui/text-field/index.d.ts → src/ui/text-field/index.ts} +8 -1
- package/src/ui/text-field/subcomponents/active-indicator.tsx +67 -0
- package/src/ui/text-field/subcomponents/floating-label.tsx +161 -0
- package/src/ui/text-field/subcomponents/leading-icon.tsx +46 -0
- package/src/ui/text-field/subcomponents/outline-container.tsx +170 -0
- package/src/ui/text-field/subcomponents/prefix-suffix.tsx +59 -0
- package/src/ui/text-field/subcomponents/supporting-text.tsx +145 -0
- package/src/ui/text-field/subcomponents/trailing-icon.tsx +199 -0
- package/src/ui/text-field/text-field.test.tsx +454 -0
- package/src/ui/text-field/text-field.tokens.ts +104 -0
- package/src/ui/text-field/text-field.tsx +548 -0
- package/src/ui/text-field/text-field.types.ts +180 -0
- package/src/ui/theme-provider/index.tsx +190 -0
- package/src/ui/toc.test.tsx +108 -0
- package/src/ui/toc.tsx +172 -0
- package/src/ui/tooltip/plain-tooltip.tsx +63 -0
- package/src/ui/tooltip/rich-tooltip.tsx +94 -0
- package/src/ui/tooltip/tooltip-box.tsx +266 -0
- package/src/ui/tooltip/tooltip-caret-shape.tsx +68 -0
- package/src/ui/tooltip/tooltip.tokens.ts +26 -0
- package/src/ui/tooltip/tooltip.types.ts +70 -0
- package/src/ui/tooltip/use-tooltip-position.ts +208 -0
- package/src/ui/tooltip/use-tooltip-state.ts +41 -0
- package/src/ui/typography/__tests__/typography.test.tsx +170 -0
- package/{dist/ui/typography/index.d.ts → src/ui/typography/index.ts} +21 -3
- package/src/ui/typography/type-scale-tokens.ts +205 -0
- package/src/ui/typography/typography-key-tokens.ts +43 -0
- package/src/ui/typography/typography-tokens.ts +360 -0
- package/src/ui/typography/typography.css +22 -0
- package/src/ui/typography/typography.tsx +559 -0
- package/test-render.tsx +4 -0
- package/test-shadow.html +26 -0
- package/test_output.txt +164 -0
- package/test_output_v2.txt +5 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +20 -0
- package/vitest.config.ts +11 -0
- package/dist/hooks/useClickOutside.d.ts +0 -8
- package/dist/hooks/useMediaQuery.d.ts +0 -11
- package/dist/hooks/useRipple.d.ts +0 -26
- package/dist/lib/material-symbols-preconnect.d.ts +0 -42
- package/dist/lib/theme-utils.d.ts +0 -63
- package/dist/lib/utils.d.ts +0 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/md3.d.ts +0 -14
- package/dist/ui/app-bar/app-bar-column.d.ts +0 -28
- package/dist/ui/app-bar/app-bar-item-button.d.ts +0 -16
- package/dist/ui/app-bar/app-bar-overflow-indicator.d.ts +0 -18
- package/dist/ui/app-bar/app-bar-row.d.ts +0 -36
- package/dist/ui/app-bar/app-bar.tokens.d.ts +0 -184
- package/dist/ui/app-bar/app-bar.types.d.ts +0 -392
- package/dist/ui/app-bar/bottom-app-bar.d.ts +0 -31
- package/dist/ui/app-bar/docked-toolbar.d.ts +0 -25
- package/dist/ui/app-bar/hooks/use-app-bar-scroll.d.ts +0 -42
- package/dist/ui/app-bar/hooks/use-flexible-app-bar.d.ts +0 -37
- package/dist/ui/app-bar/large-flexible-app-bar.d.ts +0 -26
- package/dist/ui/app-bar/medium-flexible-app-bar.d.ts +0 -28
- package/dist/ui/app-bar/search-app-bar.d.ts +0 -43
- package/dist/ui/app-bar/search-view.d.ts +0 -54
- package/dist/ui/app-bar/small-app-bar.d.ts +0 -37
- package/dist/ui/badge.d.ts +0 -125
- package/dist/ui/button-group.d.ts +0 -59
- package/dist/ui/button.d.ts +0 -148
- package/dist/ui/card.d.ts +0 -62
- package/dist/ui/checkbox.d.ts +0 -82
- package/dist/ui/chip.d.ts +0 -110
- package/dist/ui/code-block.d.ts +0 -14
- package/dist/ui/dialog.d.ts +0 -111
- package/dist/ui/divider.d.ts +0 -164
- package/dist/ui/drawer.d.ts +0 -39
- package/dist/ui/dropdown.d.ts +0 -29
- package/dist/ui/fab-menu.d.ts +0 -204
- package/dist/ui/fab.d.ts +0 -162
- package/dist/ui/icon-button.d.ts +0 -131
- package/dist/ui/icon.d.ts +0 -88
- package/dist/ui/loading-indicator.d.ts +0 -42
- package/dist/ui/navigation-rail.d.ts +0 -29
- package/dist/ui/progress-indicator/circular.d.ts +0 -3
- package/dist/ui/progress-indicator/hooks.d.ts +0 -3
- package/dist/ui/progress-indicator/linear-flat.d.ts +0 -10
- package/dist/ui/progress-indicator/linear-wavy.d.ts +0 -18
- package/dist/ui/progress-indicator/linear.d.ts +0 -3
- package/dist/ui/progress-indicator/types.d.ts +0 -151
- package/dist/ui/progress-indicator/utils.d.ts +0 -3
- package/dist/ui/radio-button.d.ts +0 -106
- package/dist/ui/ripple.d.ts +0 -126
- package/dist/ui/scroll-area.d.ts +0 -27
- package/dist/ui/search/animated-placeholder.d.ts +0 -54
- package/dist/ui/search/hooks/use-search-keyboard.d.ts +0 -32
- package/dist/ui/search/hooks/use-search-view-focus.d.ts +0 -6
- package/dist/ui/search/index.d.ts +0 -27
- package/dist/ui/search/search-bar.d.ts +0 -32
- package/dist/ui/search/search-context.d.ts +0 -24
- package/dist/ui/search/search-view-docked.d.ts +0 -25
- package/dist/ui/search/search-view-fullscreen.d.ts +0 -36
- package/dist/ui/search/search.d.ts +0 -50
- package/dist/ui/search/search.tokens.d.ts +0 -112
- package/dist/ui/search/search.types.d.ts +0 -131
- package/dist/ui/search/trailing-action.d.ts +0 -9
- package/dist/ui/shared/constants.d.ts +0 -86
- package/dist/ui/slider/hooks/useSliderMath.d.ts +0 -101
- package/dist/ui/slider/range-slider.d.ts +0 -47
- package/dist/ui/slider/slider-thumb.d.ts +0 -33
- package/dist/ui/slider/slider-track.d.ts +0 -25
- package/dist/ui/slider/slider.d.ts +0 -60
- package/dist/ui/slider/slider.tokens.d.ts +0 -151
- package/dist/ui/slider/slider.types.d.ts +0 -259
- package/dist/ui/snackbar/index.d.ts +0 -6
- package/dist/ui/snackbar/snackbar.d.ts +0 -197
- package/dist/ui/switch/switch.d.ts +0 -30
- package/dist/ui/switch/switch.stories.d.ts +0 -48
- package/dist/ui/switch/switch.tokens.d.ts +0 -67
- package/dist/ui/switch/switch.types.d.ts +0 -59
- package/dist/ui/tabs/tab.d.ts +0 -43
- package/dist/ui/tabs/tabs-content.d.ts +0 -36
- package/dist/ui/tabs/tabs-list.d.ts +0 -40
- package/dist/ui/tabs/tabs.d.ts +0 -60
- package/dist/ui/tabs/tabs.tokens.d.ts +0 -94
- package/dist/ui/tabs/tabs.types.d.ts +0 -172
- package/dist/ui/text-field/subcomponents/active-indicator.d.ts +0 -24
- package/dist/ui/text-field/subcomponents/floating-label.d.ts +0 -43
- package/dist/ui/text-field/subcomponents/leading-icon.d.ts +0 -23
- package/dist/ui/text-field/subcomponents/outline-container.d.ts +0 -42
- package/dist/ui/text-field/subcomponents/prefix-suffix.d.ts +0 -24
- package/dist/ui/text-field/subcomponents/supporting-text.d.ts +0 -37
- package/dist/ui/text-field/subcomponents/trailing-icon.d.ts +0 -41
- package/dist/ui/text-field/text-field.d.ts +0 -49
- package/dist/ui/text-field/text-field.tokens.d.ts +0 -76
- package/dist/ui/text-field/text-field.types.d.ts +0 -126
- package/dist/ui/theme-provider/index.d.ts +0 -48
- package/dist/ui/toc.d.ts +0 -80
- package/dist/ui/tooltip/plain-tooltip.d.ts +0 -2
- package/dist/ui/tooltip/rich-tooltip.d.ts +0 -2
- package/dist/ui/tooltip/tooltip-box.d.ts +0 -2
- package/dist/ui/tooltip/tooltip-caret-shape.d.ts +0 -9
- package/dist/ui/tooltip/tooltip.tokens.d.ts +0 -26
- package/dist/ui/tooltip/tooltip.types.d.ts +0 -56
- package/dist/ui/tooltip/use-tooltip-position.d.ts +0 -8
- package/dist/ui/tooltip/use-tooltip-state.d.ts +0 -2
- package/dist/ui/typography/type-scale-tokens.d.ts +0 -162
- package/dist/ui/typography/typography-key-tokens.d.ts +0 -40
- package/dist/ui/typography/typography-tokens.d.ts +0 -220
- package/dist/ui/typography/typography.d.ts +0 -265
- /package/{dist/hooks/index.d.ts → src/hooks/index.ts} +0 -0
- /package/{dist/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { Icon } from "../ui/icon";
|
|
5
|
+
|
|
6
|
+
describe("Icon Component", () => {
|
|
7
|
+
it("renders successfully with a name", () => {
|
|
8
|
+
render(<Icon name="home" />);
|
|
9
|
+
const icon = screen.getByText("home");
|
|
10
|
+
expect(icon).toBeInTheDocument();
|
|
11
|
+
expect(icon.tagName).toBe("SPAN");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("applies default styles and attributes", () => {
|
|
15
|
+
render(<Icon name="home" />);
|
|
16
|
+
const icon = screen.getByText("home");
|
|
17
|
+
|
|
18
|
+
expect(icon).toHaveClass("md-icon", "select-none");
|
|
19
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
20
|
+
|
|
21
|
+
expect(icon).toHaveStyle({
|
|
22
|
+
fontFamily: "'Material Symbols Outlined'",
|
|
23
|
+
fontSize: "24px",
|
|
24
|
+
fontVariationSettings: "'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders different variants correctly", () => {
|
|
29
|
+
const { rerender } = render(<Icon name="home" variant="rounded" />);
|
|
30
|
+
let icon = screen.getByText("home");
|
|
31
|
+
expect(icon).toHaveStyle({ fontFamily: "'Material Symbols Rounded'" });
|
|
32
|
+
|
|
33
|
+
rerender(<Icon name="home" variant="sharp" />);
|
|
34
|
+
icon = screen.getByText("home");
|
|
35
|
+
expect(icon).toHaveStyle({ fontFamily: "'Material Symbols Sharp'" });
|
|
36
|
+
|
|
37
|
+
rerender(<Icon name="home" variant="outlined" />);
|
|
38
|
+
icon = screen.getByText("home");
|
|
39
|
+
expect(icon).toHaveStyle({ fontFamily: "'Material Symbols Outlined'" });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("updates font-variation-settings based on axes props", () => {
|
|
43
|
+
render(
|
|
44
|
+
<Icon
|
|
45
|
+
name="settings"
|
|
46
|
+
fill={1}
|
|
47
|
+
weight={700}
|
|
48
|
+
grade={200}
|
|
49
|
+
opticalSize={48}
|
|
50
|
+
/>,
|
|
51
|
+
);
|
|
52
|
+
const icon = screen.getByText("settings");
|
|
53
|
+
expect(icon).toHaveStyle({
|
|
54
|
+
fontVariationSettings: "'FILL' 1, 'wght' 700, 'GRAD' 200, 'opsz' 48",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("overrides font-size when size prop is provided", () => {
|
|
59
|
+
render(<Icon name="home" size={32} opticalSize={40} />);
|
|
60
|
+
const icon = screen.getByText("home");
|
|
61
|
+
expect(icon).toHaveStyle({ fontSize: "32px" });
|
|
62
|
+
// Ensure opsz axis still uses opticalSize
|
|
63
|
+
expect(icon.style.fontVariationSettings).toContain("'opsz' 40");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("forwards ref correctly", () => {
|
|
67
|
+
const ref = React.createRef<HTMLSpanElement>();
|
|
68
|
+
render(<Icon name="home" ref={ref} />);
|
|
69
|
+
expect(ref.current).not.toBeNull();
|
|
70
|
+
expect(ref.current?.tagName).toBe("SPAN");
|
|
71
|
+
expect(ref.current?.textContent).toBe("home");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("renders with animateFill without crashing", () => {
|
|
75
|
+
render(<Icon name="favorite" fill={1} animateFill />);
|
|
76
|
+
const icon = screen.getByText("favorite");
|
|
77
|
+
expect(icon).toBeInTheDocument();
|
|
78
|
+
// In a jsdom environment, complex motion logic might not fully 'run'
|
|
79
|
+
// but we verify the component renders and has the correct styles.
|
|
80
|
+
const style = window.getComputedStyle(icon);
|
|
81
|
+
expect(style.fontVariationSettings).toBe(
|
|
82
|
+
"'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24",
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("passes through extra HTML attributes", () => {
|
|
87
|
+
render(<Icon name="home" data-testid="custom-icon" id="my-icon" />);
|
|
88
|
+
const icon = screen.getByTestId("custom-icon");
|
|
89
|
+
expect(icon).toHaveAttribute("id", "my-icon");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { LoadingIndicator } from "../ui/loading-indicator";
|
|
4
|
+
|
|
5
|
+
describe("LoadingIndicator", () => {
|
|
6
|
+
// ─── A11y: ARIA Attributes ───────────────────────────────────────────────
|
|
7
|
+
it("renders with role='progressbar'", () => {
|
|
8
|
+
render(<LoadingIndicator aria-label="Loading content" />);
|
|
9
|
+
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("applies required aria-label", () => {
|
|
13
|
+
render(<LoadingIndicator aria-label="Loading news article" />);
|
|
14
|
+
const el = screen.getByRole("progressbar");
|
|
15
|
+
expect(el).toHaveAttribute("aria-label", "Loading news article");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("sets aria-valuemin and aria-valuemax", () => {
|
|
19
|
+
render(<LoadingIndicator aria-label="Loading" />);
|
|
20
|
+
const el = screen.getByRole("progressbar");
|
|
21
|
+
expect(el).toHaveAttribute("aria-valuemin", "0");
|
|
22
|
+
expect(el).toHaveAttribute("aria-valuemax", "100");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("does not have aria-valuenow in indeterminate mode", () => {
|
|
26
|
+
render(<LoadingIndicator aria-label="Loading" />);
|
|
27
|
+
expect(screen.getByRole("progressbar")).not.toHaveAttribute(
|
|
28
|
+
"aria-valuenow",
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ─── Determinate Mode ────────────────────────────────────────────────────
|
|
33
|
+
it("sets aria-valuenow when progress is provided", () => {
|
|
34
|
+
render(<LoadingIndicator aria-label="Loading" progress={0.5} />);
|
|
35
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
36
|
+
"aria-valuenow",
|
|
37
|
+
"50",
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("clamps aria-valuenow at 0 for negative progress", () => {
|
|
42
|
+
render(<LoadingIndicator aria-label="Loading" progress={-0.5} />);
|
|
43
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
44
|
+
"aria-valuenow",
|
|
45
|
+
"0",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("clamps aria-valuenow at 100 for progress > 1", () => {
|
|
50
|
+
render(<LoadingIndicator aria-label="Loading" progress={1.5} />);
|
|
51
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
52
|
+
"aria-valuenow",
|
|
53
|
+
"100",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("renders static path (no SMIL) in determinate mode", () => {
|
|
58
|
+
const { container } = render(
|
|
59
|
+
<LoadingIndicator aria-label="Loading" progress={0.5} />,
|
|
60
|
+
);
|
|
61
|
+
const path = container.querySelector("path");
|
|
62
|
+
expect(path).toBeInTheDocument();
|
|
63
|
+
// Determinate mode has no SMIL animate child
|
|
64
|
+
const animateEl = path?.querySelector("animate");
|
|
65
|
+
expect(animateEl).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ─── Indeterminate Mode (SMIL) ───────────────────────────────────────────
|
|
69
|
+
it("renders SVG path with SMIL shape morphing animation in indeterminate mode", async () => {
|
|
70
|
+
const { container } = render(<LoadingIndicator aria-label="Loading" />);
|
|
71
|
+
const path = container.querySelector("path");
|
|
72
|
+
expect(path).toBeInTheDocument();
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
const animateEl = path?.querySelector("animate[attributeName='d']");
|
|
75
|
+
expect(animateEl).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ─── Variants ────────────────────────────────────────────────────────────
|
|
80
|
+
it("renders default uncontained variant without container background", () => {
|
|
81
|
+
const { container } = render(<LoadingIndicator aria-label="Loading" />);
|
|
82
|
+
const inner = container.querySelector(".rounded-full");
|
|
83
|
+
expect(inner).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("renders contained variant with a circular container", () => {
|
|
87
|
+
const { container } = render(
|
|
88
|
+
<LoadingIndicator variant="contained" aria-label="Loading" />,
|
|
89
|
+
);
|
|
90
|
+
const inner = container.querySelector(".rounded-full");
|
|
91
|
+
expect(inner).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ─── Responsive Sizing ───────────────────────────────────────────────────
|
|
95
|
+
it("applies custom size via style", () => {
|
|
96
|
+
render(<LoadingIndicator size={96} aria-label="Loading" />);
|
|
97
|
+
const el = screen.getByRole("progressbar");
|
|
98
|
+
expect(el.style.width).toBe("96px");
|
|
99
|
+
expect(el.style.height).toBe("96px");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("clamps size to 24dp minimum", () => {
|
|
103
|
+
render(<LoadingIndicator size={8} aria-label="Loading" />);
|
|
104
|
+
const el = screen.getByRole("progressbar");
|
|
105
|
+
expect(el.style.width).toBe("24px");
|
|
106
|
+
expect(el.style.height).toBe("24px");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("clamps size to 240dp maximum", () => {
|
|
110
|
+
render(<LoadingIndicator size={999} aria-label="Loading" />);
|
|
111
|
+
const el = screen.getByRole("progressbar");
|
|
112
|
+
expect(el.style.width).toBe("240px");
|
|
113
|
+
expect(el.style.height).toBe("240px");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ─── General ─────────────────────────────────────────────────────────────
|
|
117
|
+
it("renders an SVG element inside", () => {
|
|
118
|
+
const { container } = render(<LoadingIndicator aria-label="Loading" />);
|
|
119
|
+
const svg = container.querySelector("svg");
|
|
120
|
+
expect(svg).toBeInTheDocument();
|
|
121
|
+
expect(svg).toHaveAttribute("aria-hidden", "true");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("merges custom className", () => {
|
|
125
|
+
render(<LoadingIndicator aria-label="Loading" className="my-custom" />);
|
|
126
|
+
expect(screen.getByRole("progressbar").className).toContain("my-custom");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { ProgressIndicator } from "../ui/progress-indicator";
|
|
4
|
+
|
|
5
|
+
describe("ProgressIndicator", () => {
|
|
6
|
+
// ── Linear Flat ──────────────────────────────────────────────────────────
|
|
7
|
+
describe("Linear - Flat", () => {
|
|
8
|
+
it("renders with role='progressbar'", () => {
|
|
9
|
+
render(
|
|
10
|
+
<ProgressIndicator variant="linear" aria-label="Uploading file" />,
|
|
11
|
+
);
|
|
12
|
+
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("applies required aria-label", () => {
|
|
16
|
+
render(
|
|
17
|
+
<ProgressIndicator variant="linear" aria-label="Uploading file" />,
|
|
18
|
+
);
|
|
19
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
20
|
+
"aria-label",
|
|
21
|
+
"Uploading file",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("shows aria-valuenow for determinate state", () => {
|
|
26
|
+
render(
|
|
27
|
+
<ProgressIndicator
|
|
28
|
+
variant="linear"
|
|
29
|
+
value={42}
|
|
30
|
+
aria-label="Downloading"
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
33
|
+
const el = screen.getByRole("progressbar");
|
|
34
|
+
expect(el).toHaveAttribute("aria-valuenow", "42");
|
|
35
|
+
expect(el).toHaveAttribute("aria-valuemin", "0");
|
|
36
|
+
expect(el).toHaveAttribute("aria-valuemax", "100");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not show aria-valuenow for indeterminate state", () => {
|
|
40
|
+
render(<ProgressIndicator variant="linear" aria-label="Loading" />);
|
|
41
|
+
expect(screen.getByRole("progressbar")).not.toHaveAttribute(
|
|
42
|
+
"aria-valuenow",
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("clamps value between 0 and 100", () => {
|
|
47
|
+
render(
|
|
48
|
+
<ProgressIndicator
|
|
49
|
+
variant="linear"
|
|
50
|
+
value={150}
|
|
51
|
+
aria-label="Overloaded"
|
|
52
|
+
/>,
|
|
53
|
+
);
|
|
54
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
55
|
+
"aria-valuenow",
|
|
56
|
+
"100",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("shows stop indicator dot in determinate mode by default", () => {
|
|
61
|
+
const { container } = render(
|
|
62
|
+
<ProgressIndicator variant="linear" value={50} aria-label="Loading" />,
|
|
63
|
+
);
|
|
64
|
+
const dot = container.querySelector(".rounded-full[aria-hidden='true']");
|
|
65
|
+
expect(dot).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("stop indicator is 4px (MD3 spec) regardless of trackHeight", () => {
|
|
69
|
+
const { container } = render(
|
|
70
|
+
<ProgressIndicator
|
|
71
|
+
variant="linear"
|
|
72
|
+
value={50}
|
|
73
|
+
trackHeight={8}
|
|
74
|
+
aria-label="Thick track"
|
|
75
|
+
/>,
|
|
76
|
+
);
|
|
77
|
+
const dot = container.querySelector(
|
|
78
|
+
".rounded-full[aria-hidden='true']",
|
|
79
|
+
) as HTMLElement | null;
|
|
80
|
+
expect(dot).toBeInTheDocument();
|
|
81
|
+
expect(dot?.style.width).toBe("4px");
|
|
82
|
+
expect(dot?.style.height).toBe("4px");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("stop indicator is NOT shown for indeterminate state", () => {
|
|
86
|
+
const { container } = render(
|
|
87
|
+
<ProgressIndicator variant="linear" aria-label="Loading" />,
|
|
88
|
+
);
|
|
89
|
+
const dot = container.querySelector(".rounded-full[aria-hidden='true']");
|
|
90
|
+
expect(dot).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("accepts space prop", () => {
|
|
94
|
+
render(
|
|
95
|
+
<ProgressIndicator
|
|
96
|
+
variant="linear"
|
|
97
|
+
value={50}
|
|
98
|
+
gapSize={8}
|
|
99
|
+
aria-label="Test space"
|
|
100
|
+
/>,
|
|
101
|
+
);
|
|
102
|
+
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("hides stop indicator when showStopIndicator=false", () => {
|
|
106
|
+
const { container } = render(
|
|
107
|
+
<ProgressIndicator
|
|
108
|
+
variant="linear"
|
|
109
|
+
value={50}
|
|
110
|
+
showStopIndicator={false}
|
|
111
|
+
aria-label="Loading"
|
|
112
|
+
/>,
|
|
113
|
+
);
|
|
114
|
+
const dot = container.querySelector(".rounded-full[aria-hidden='true']");
|
|
115
|
+
expect(dot).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("merges custom className", () => {
|
|
119
|
+
render(
|
|
120
|
+
<ProgressIndicator
|
|
121
|
+
variant="linear"
|
|
122
|
+
aria-label="Loading"
|
|
123
|
+
className="test-class"
|
|
124
|
+
/>,
|
|
125
|
+
);
|
|
126
|
+
expect(screen.getByRole("progressbar").className).toContain("test-class");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("active indicator has minWidth (dot) when value is 0 — MD3 spec", () => {
|
|
130
|
+
// MD3 spec: "When progress first begins, the active indicator appears as a dot."
|
|
131
|
+
const { container } = render(
|
|
132
|
+
<ProgressIndicator variant="linear" value={0} aria-label="Starting" />,
|
|
133
|
+
);
|
|
134
|
+
// The animated div inside the track should have minWidth set (not zero)
|
|
135
|
+
// Track background is the first absolute div, active is the second absolute div.
|
|
136
|
+
const track = container.querySelectorAll(".relative.w-full .absolute")[1];
|
|
137
|
+
expect(track).toBeInTheDocument();
|
|
138
|
+
const style = (track as HTMLElement)?.style;
|
|
139
|
+
// minWidth should be the trackHeight default (4px)
|
|
140
|
+
expect(style?.minWidth).toBe("4px");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ── Linear Wavy ──────────────────────────────────────────────────────────
|
|
145
|
+
describe("Linear - Wavy", () => {
|
|
146
|
+
it("renders SVG for wavy shape", () => {
|
|
147
|
+
const { container } = render(
|
|
148
|
+
<ProgressIndicator
|
|
149
|
+
variant="linear"
|
|
150
|
+
shape="wavy"
|
|
151
|
+
aria-label="Loading wavy"
|
|
152
|
+
/>,
|
|
153
|
+
);
|
|
154
|
+
const svg = container.querySelector("svg");
|
|
155
|
+
expect(svg).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("accepts custom amplitude and wavelength", () => {
|
|
159
|
+
const { container } = render(
|
|
160
|
+
<ProgressIndicator
|
|
161
|
+
variant="linear"
|
|
162
|
+
shape="wavy"
|
|
163
|
+
amplitude={10}
|
|
164
|
+
wavelength={40}
|
|
165
|
+
aria-label="Loading wavy custom"
|
|
166
|
+
/>,
|
|
167
|
+
);
|
|
168
|
+
expect(container.querySelector("svg")).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("SVG has overflow=visible to prevent wave clip (MD3)", () => {
|
|
172
|
+
const { container } = render(
|
|
173
|
+
<ProgressIndicator
|
|
174
|
+
variant="linear"
|
|
175
|
+
shape="wavy"
|
|
176
|
+
aria-label="Loading wavy overflow"
|
|
177
|
+
/>,
|
|
178
|
+
);
|
|
179
|
+
const svg = container.querySelector("svg") as SVGElement | null;
|
|
180
|
+
expect(svg?.style.overflow).toBe("visible");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("accepts indeterminateWavelength prop", () => {
|
|
184
|
+
const { container } = render(
|
|
185
|
+
<ProgressIndicator
|
|
186
|
+
variant="linear"
|
|
187
|
+
shape="wavy"
|
|
188
|
+
indeterminateWavelength={30}
|
|
189
|
+
aria-label="Custom indeterminate wavelength"
|
|
190
|
+
/>,
|
|
191
|
+
);
|
|
192
|
+
expect(container.querySelector("svg")).toBeInTheDocument();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("does NOT show stop indicator for wavy indeterminate — MD3 spec", () => {
|
|
196
|
+
const { container } = render(
|
|
197
|
+
<ProgressIndicator
|
|
198
|
+
variant="linear"
|
|
199
|
+
shape="wavy"
|
|
200
|
+
aria-label="Loading wavy no stop"
|
|
201
|
+
/>,
|
|
202
|
+
);
|
|
203
|
+
const dot = container.querySelector(".rounded-full[aria-hidden='true']");
|
|
204
|
+
expect(dot).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ── Circular ─────────────────────────────────────────────────────────────
|
|
209
|
+
describe("Circular", () => {
|
|
210
|
+
it("renders with role='progressbar'", () => {
|
|
211
|
+
render(
|
|
212
|
+
<ProgressIndicator variant="circular" aria-label="Loading circular" />,
|
|
213
|
+
);
|
|
214
|
+
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("renders SVG with circle elements", () => {
|
|
218
|
+
const { container } = render(
|
|
219
|
+
<ProgressIndicator variant="circular" aria-label="Loading circular" />,
|
|
220
|
+
);
|
|
221
|
+
const circles = container.querySelectorAll("circle");
|
|
222
|
+
expect(circles.length).toBeGreaterThanOrEqual(2);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("shows aria-valuenow for determinate state", () => {
|
|
226
|
+
render(
|
|
227
|
+
<ProgressIndicator
|
|
228
|
+
variant="circular"
|
|
229
|
+
value={75}
|
|
230
|
+
aria-label="Loading 75%"
|
|
231
|
+
/>,
|
|
232
|
+
);
|
|
233
|
+
expect(screen.getByRole("progressbar")).toHaveAttribute(
|
|
234
|
+
"aria-valuenow",
|
|
235
|
+
"75",
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("applies custom size via inline style", () => {
|
|
240
|
+
render(
|
|
241
|
+
<ProgressIndicator variant="circular" size={64} aria-label="Loading" />,
|
|
242
|
+
);
|
|
243
|
+
const el = screen.getByRole("progressbar");
|
|
244
|
+
expect(el.style.width).toBe("64px");
|
|
245
|
+
expect(el.style.height).toBe("64px");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("does not show aria-valuenow for indeterminate", () => {
|
|
249
|
+
render(<ProgressIndicator variant="circular" aria-label="Loading" />);
|
|
250
|
+
expect(screen.getByRole("progressbar")).not.toHaveAttribute(
|
|
251
|
+
"aria-valuenow",
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// ── Circular Wavy ────────────────────────────────────────────────────────
|
|
257
|
+
describe("Circular - Wavy", () => {
|
|
258
|
+
it("renders SVG with path element for wavy shape", () => {
|
|
259
|
+
const { container } = render(
|
|
260
|
+
<ProgressIndicator
|
|
261
|
+
variant="circular"
|
|
262
|
+
shape="wavy"
|
|
263
|
+
aria-label="Loading circular wavy"
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
const svg = container.querySelector("svg");
|
|
267
|
+
expect(svg).toBeInTheDocument();
|
|
268
|
+
const path = container.querySelector("path");
|
|
269
|
+
expect(path).toBeInTheDocument();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("accepts custom amplitude and wavelength", () => {
|
|
273
|
+
const { container } = render(
|
|
274
|
+
<ProgressIndicator
|
|
275
|
+
variant="circular"
|
|
276
|
+
shape="wavy"
|
|
277
|
+
amplitude={8}
|
|
278
|
+
wavelength={40}
|
|
279
|
+
aria-label="Loading circular wavy custom"
|
|
280
|
+
/>,
|
|
281
|
+
);
|
|
282
|
+
const path = container.querySelector("path");
|
|
283
|
+
expect(path).toBeInTheDocument();
|
|
284
|
+
// verify that path starts with M (move layout command generated mathematically)
|
|
285
|
+
expect(path?.getAttribute("d")?.startsWith("M ")).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ── RTL Support ──────────────────────────────────────────────────────────
|
|
290
|
+
describe("RTL support", () => {
|
|
291
|
+
it("renders linear inside RTL container without crashing", () => {
|
|
292
|
+
const { container } = render(
|
|
293
|
+
<div dir="rtl">
|
|
294
|
+
<ProgressIndicator
|
|
295
|
+
variant="linear"
|
|
296
|
+
value={30}
|
|
297
|
+
aria-label="RTL progress"
|
|
298
|
+
/>
|
|
299
|
+
</div>,
|
|
300
|
+
);
|
|
301
|
+
expect(
|
|
302
|
+
container.querySelector("[role='progressbar']"),
|
|
303
|
+
).toBeInTheDocument();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
// Mock ResizeObserver for Radix UI (must be a constructor and trigger callback)
|
|
5
|
+
class ResizeObserverMock implements ResizeObserver {
|
|
6
|
+
private callback: ResizeObserverCallback;
|
|
7
|
+
|
|
8
|
+
constructor(callback: ResizeObserverCallback) {
|
|
9
|
+
this.callback = callback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
observe(element: Element, _options?: ResizeObserverOptions) {
|
|
13
|
+
// Immediately trigger callback with some dimensions to trick Radix
|
|
14
|
+
this.callback(
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
target: element,
|
|
18
|
+
contentRect: {
|
|
19
|
+
height: 1000,
|
|
20
|
+
width: 1000,
|
|
21
|
+
x: 0,
|
|
22
|
+
y: 0,
|
|
23
|
+
top: 0,
|
|
24
|
+
right: 1000,
|
|
25
|
+
bottom: 1000,
|
|
26
|
+
left: 0,
|
|
27
|
+
toJSON: () => ({}),
|
|
28
|
+
},
|
|
29
|
+
borderBoxSize: [],
|
|
30
|
+
contentBoxSize: [],
|
|
31
|
+
devicePixelContentBoxSize: [],
|
|
32
|
+
} as ResizeObserverEntry,
|
|
33
|
+
],
|
|
34
|
+
this,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
unobserve = vi.fn((_element: Element) => {});
|
|
38
|
+
disconnect = vi.fn(() => {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
vi.stubGlobal("ResizeObserver", ResizeObserverMock);
|
|
42
|
+
|
|
43
|
+
// Mock PointerEvent which is missing in JSDOM but used by Radix
|
|
44
|
+
if (!globalThis.PointerEvent) {
|
|
45
|
+
class PointerEventMock extends MouseEvent {
|
|
46
|
+
constructor(type: string, params: PointerEventInit = {}) {
|
|
47
|
+
super(type, params);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Use any here for global registration as it's a known environment issue
|
|
51
|
+
// but we cast it as unknown first to satisfy some strict linting
|
|
52
|
+
vi.stubGlobal(
|
|
53
|
+
"PointerEvent",
|
|
54
|
+
PointerEventMock as unknown as typeof PointerEvent,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Mock HTMLElement prototype to return dimensions
|
|
59
|
+
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
|
|
60
|
+
get() {
|
|
61
|
+
return this.parentNode;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Mock IntersectionObserver
|
|
66
|
+
class IntersectionObserverMock implements IntersectionObserver {
|
|
67
|
+
readonly root: Element | null = null;
|
|
68
|
+
readonly rootMargin: string = "";
|
|
69
|
+
readonly thresholds: ReadonlyArray<number> = [];
|
|
70
|
+
constructor(
|
|
71
|
+
public callback: IntersectionObserverCallback,
|
|
72
|
+
_options?: IntersectionObserverInit,
|
|
73
|
+
) {}
|
|
74
|
+
observe = vi.fn((_element: Element) => {});
|
|
75
|
+
unobserve = vi.fn((_element: Element) => {});
|
|
76
|
+
disconnect = vi.fn(() => {});
|
|
77
|
+
takeRecords = vi.fn(() => []);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
vi.stubGlobal("IntersectionObserver", IntersectionObserverMock);
|