@ayasofyazilim/ui 0.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.
Files changed (236) hide show
  1. package/__mocks__/canvas.ts +8 -0
  2. package/components.json +21 -0
  3. package/eslint.config.js +4 -0
  4. package/jest-environment.js +37 -0
  5. package/jest.config.ts +47 -0
  6. package/jest.setup.ts +69 -0
  7. package/package.json +124 -0
  8. package/postcss.config.mjs +6 -0
  9. package/src/aria/index.tsx +1 -0
  10. package/src/aria/number-field.tsx +41 -0
  11. package/src/components/.gitkeep +0 -0
  12. package/src/components/accordion.tsx +66 -0
  13. package/src/components/alert-dialog.tsx +157 -0
  14. package/src/components/alert.tsx +70 -0
  15. package/src/components/aspect-ratio.tsx +11 -0
  16. package/src/components/avatar.tsx +53 -0
  17. package/src/components/badge.tsx +67 -0
  18. package/src/components/breadcrumb.tsx +109 -0
  19. package/src/components/button-group.tsx +83 -0
  20. package/src/components/button.tsx +68 -0
  21. package/src/components/calendar.tsx +219 -0
  22. package/src/components/card.tsx +92 -0
  23. package/src/components/carousel.tsx +241 -0
  24. package/src/components/chart.tsx +363 -0
  25. package/src/components/checkbox.tsx +32 -0
  26. package/src/components/collapsible.tsx +33 -0
  27. package/src/components/command.tsx +184 -0
  28. package/src/components/context-menu.tsx +252 -0
  29. package/src/components/dialog.tsx +144 -0
  30. package/src/components/drawer.tsx +135 -0
  31. package/src/components/dropdown-menu.tsx +258 -0
  32. package/src/components/empty.tsx +100 -0
  33. package/src/components/field.tsx +248 -0
  34. package/src/components/form.tsx +169 -0
  35. package/src/components/hover-card.tsx +44 -0
  36. package/src/components/input-group.tsx +170 -0
  37. package/src/components/input-otp.tsx +77 -0
  38. package/src/components/input.tsx +21 -0
  39. package/src/components/item.tsx +193 -0
  40. package/src/components/kbd.tsx +28 -0
  41. package/src/components/label.tsx +24 -0
  42. package/src/components/menubar.tsx +276 -0
  43. package/src/components/navigation-menu.tsx +168 -0
  44. package/src/components/pagination.tsx +130 -0
  45. package/src/components/popover.tsx +88 -0
  46. package/src/components/progress.tsx +31 -0
  47. package/src/components/radio-group.tsx +45 -0
  48. package/src/components/resizable.tsx +56 -0
  49. package/src/components/scroll-area.tsx +58 -0
  50. package/src/components/select.tsx +189 -0
  51. package/src/components/separator.tsx +28 -0
  52. package/src/components/sheet.tsx +140 -0
  53. package/src/components/sidebar.tsx +862 -0
  54. package/src/components/skeleton.tsx +13 -0
  55. package/src/components/slider.tsx +63 -0
  56. package/src/components/sonner.tsx +40 -0
  57. package/src/components/spinner.tsx +16 -0
  58. package/src/components/stepper.tsx +291 -0
  59. package/src/components/switch.tsx +31 -0
  60. package/src/components/table.tsx +133 -0
  61. package/src/components/tabs.tsx +66 -0
  62. package/src/components/textarea.tsx +18 -0
  63. package/src/components/toggle-group.tsx +83 -0
  64. package/src/components/toggle.tsx +47 -0
  65. package/src/components/tooltip.tsx +66 -0
  66. package/src/custom/action-button.tsx +48 -0
  67. package/src/custom/async-select.tsx +287 -0
  68. package/src/custom/awesome-not-found.tsx +116 -0
  69. package/src/custom/charts/area-chart.tsx +147 -0
  70. package/src/custom/charts/bar-chart.tsx +233 -0
  71. package/src/custom/charts/chart-card.tsx +103 -0
  72. package/src/custom/charts/index.tsx +16 -0
  73. package/src/custom/charts/pie-chart.tsx +168 -0
  74. package/src/custom/charts/radar-chart.tsx +126 -0
  75. package/src/custom/checkbox-tree.tsx +100 -0
  76. package/src/custom/combobox.tsx +296 -0
  77. package/src/custom/confirm-dialog.tsx +102 -0
  78. package/src/custom/country-selector.tsx +204 -0
  79. package/src/custom/date-picker/calendar-rac.tsx +109 -0
  80. package/src/custom/date-picker/datefield-rac.tsx +84 -0
  81. package/src/custom/date-picker/index.tsx +273 -0
  82. package/src/custom/date-picker/types/index.ts +4 -0
  83. package/src/custom/date-picker/utils/index.ts +42 -0
  84. package/src/custom/date-picker-old.tsx +50 -0
  85. package/src/custom/date-tooltip.tsx +98 -0
  86. package/src/custom/document-scanner/consts.ts +5 -0
  87. package/src/custom/document-scanner/corner-adjustment/action-buttons.tsx +33 -0
  88. package/src/custom/document-scanner/corner-adjustment/corner-handle.tsx +43 -0
  89. package/src/custom/document-scanner/corner-adjustment/hooks/use-corner-drag.ts +85 -0
  90. package/src/custom/document-scanner/corner-adjustment/index.tsx +125 -0
  91. package/src/custom/document-scanner/corner-adjustment/types.ts +53 -0
  92. package/src/custom/document-scanner/corner-adjustment/utils/clip-path.ts +22 -0
  93. package/src/custom/document-scanner/corner-adjustment/zoom-magnifier.tsx +115 -0
  94. package/src/custom/document-scanner/hooks/use-document-capture.ts +81 -0
  95. package/src/custom/document-scanner/hooks/use-document-scanner.ts +80 -0
  96. package/src/custom/document-scanner/hooks/use-perspective-crop.ts +38 -0
  97. package/src/custom/document-scanner/index.tsx +255 -0
  98. package/src/custom/document-scanner/lib.ts +407 -0
  99. package/src/custom/document-scanner/types.ts +205 -0
  100. package/src/custom/document-scanner/utils/perspective-correction.ts +139 -0
  101. package/src/custom/document-viewer/controllers.tsx +98 -0
  102. package/src/custom/document-viewer/index.tsx +43 -0
  103. package/src/custom/document-viewer/renderers/image.tsx +37 -0
  104. package/src/custom/document-viewer/renderers/index.tsx +2 -0
  105. package/src/custom/document-viewer/renderers/pdf.tsx +105 -0
  106. package/src/custom/email-input/domains.json +159 -0
  107. package/src/custom/email-input/email.tsx +229 -0
  108. package/src/custom/email-input/index.tsx +4 -0
  109. package/src/custom/email-input/types.ts +104 -0
  110. package/src/custom/file-uploader.tsx +541 -0
  111. package/src/custom/filter-component/fields/async-select.tsx +33 -0
  112. package/src/custom/filter-component/fields/date.tsx +60 -0
  113. package/src/custom/filter-component/fields/multi-select.tsx +30 -0
  114. package/src/custom/filter-component/index.tsx +217 -0
  115. package/src/custom/image-canvas.tsx +260 -0
  116. package/src/custom/json-editor.tsx +22 -0
  117. package/src/custom/master-data-grid/components/dialogs/column-settings-dialog.tsx +100 -0
  118. package/src/custom/master-data-grid/components/dialogs/index.ts +1 -0
  119. package/src/custom/master-data-grid/components/filters/client-filter.tsx +368 -0
  120. package/src/custom/master-data-grid/components/filters/filter-input.tsx +256 -0
  121. package/src/custom/master-data-grid/components/filters/index.ts +3 -0
  122. package/src/custom/master-data-grid/components/filters/inline-column-filter.tsx +233 -0
  123. package/src/custom/master-data-grid/components/filters/multi-filter-dialog.tsx +90 -0
  124. package/src/custom/master-data-grid/components/filters/server-filter.tsx +255 -0
  125. package/src/custom/master-data-grid/components/master-data-grid.tsx +472 -0
  126. package/src/custom/master-data-grid/components/pagination/index.ts +1 -0
  127. package/src/custom/master-data-grid/components/pagination/pagination.tsx +178 -0
  128. package/src/custom/master-data-grid/components/table/cell-renderer.tsx +634 -0
  129. package/src/custom/master-data-grid/components/table/header-cell.tsx +162 -0
  130. package/src/custom/master-data-grid/components/table/index.ts +4 -0
  131. package/src/custom/master-data-grid/components/table/table-body-renderer.tsx +113 -0
  132. package/src/custom/master-data-grid/components/table/virtual-body.tsx +138 -0
  133. package/src/custom/master-data-grid/components/toolbar/index.ts +1 -0
  134. package/src/custom/master-data-grid/components/toolbar/toolbar.tsx +314 -0
  135. package/src/custom/master-data-grid/hooks/index.ts +3 -0
  136. package/src/custom/master-data-grid/hooks/use-columns.tsx +332 -0
  137. package/src/custom/master-data-grid/hooks/use-editing.ts +106 -0
  138. package/src/custom/master-data-grid/hooks/use-table-state-reducer.ts +157 -0
  139. package/src/custom/master-data-grid/hooks/use-table-state.ts +31 -0
  140. package/src/custom/master-data-grid/index.ts +16 -0
  141. package/src/custom/master-data-grid/types.ts +466 -0
  142. package/src/custom/master-data-grid/utils/column-generator.tsx +306 -0
  143. package/src/custom/master-data-grid/utils/export-utils.ts +67 -0
  144. package/src/custom/master-data-grid/utils/filter-fns.ts +290 -0
  145. package/src/custom/master-data-grid/utils/index.ts +8 -0
  146. package/src/custom/master-data-grid/utils/pinning-utils.ts +88 -0
  147. package/src/custom/master-data-grid/utils/translation-utils.ts +42 -0
  148. package/src/custom/multi-select.tsx +432 -0
  149. package/src/custom/password-input.tsx +194 -0
  150. package/src/custom/phone-input.tsx +172 -0
  151. package/src/custom/schema-form/custom/index.tsx +1 -0
  152. package/src/custom/schema-form/custom/label.tsx +53 -0
  153. package/src/custom/schema-form/fields/base-input-field.tsx +82 -0
  154. package/src/custom/schema-form/fields/field.tsx +67 -0
  155. package/src/custom/schema-form/fields/index.tsx +5 -0
  156. package/src/custom/schema-form/fields/object.tsx +12 -0
  157. package/src/custom/schema-form/fields/table-array/array-field-item.tsx +90 -0
  158. package/src/custom/schema-form/fields/table-array/array-field-template.tsx +115 -0
  159. package/src/custom/schema-form/index.tsx +259 -0
  160. package/src/custom/schema-form/templates/description.tsx +20 -0
  161. package/src/custom/schema-form/templates/index.tsx +2 -0
  162. package/src/custom/schema-form/templates/submit.tsx +32 -0
  163. package/src/custom/schema-form/types.ts +64 -0
  164. package/src/custom/schema-form/utils/index.ts +4 -0
  165. package/src/custom/schema-form/utils/schema-dependency.ts +655 -0
  166. package/src/custom/schema-form/utils/schemas.ts +289 -0
  167. package/src/custom/schema-form/utils/validation.ts +23 -0
  168. package/src/custom/schema-form/widgets/boolean.tsx +77 -0
  169. package/src/custom/schema-form/widgets/combobox.tsx +274 -0
  170. package/src/custom/schema-form/widgets/date.tsx +59 -0
  171. package/src/custom/schema-form/widgets/email.tsx +34 -0
  172. package/src/custom/schema-form/widgets/index.tsx +10 -0
  173. package/src/custom/schema-form/widgets/password.tsx +40 -0
  174. package/src/custom/schema-form/widgets/phone.tsx +40 -0
  175. package/src/custom/schema-form/widgets/select.tsx +105 -0
  176. package/src/custom/schema-form/widgets/selectable.tsx +25 -0
  177. package/src/custom/schema-form/widgets/string-array.tsx +296 -0
  178. package/src/custom/schema-form/widgets/url.tsx +56 -0
  179. package/src/custom/section-layout-v2.tsx +212 -0
  180. package/src/custom/select-tabs.tsx +109 -0
  181. package/src/custom/selectable.tsx +316 -0
  182. package/src/custom/stepper.tsx +236 -0
  183. package/src/custom/tab-layout.tsx +213 -0
  184. package/src/custom/tanstack-table/fields/index.tsx +12 -0
  185. package/src/custom/tanstack-table/fields/tanstack-table-action-dialogs.tsx +89 -0
  186. package/src/custom/tanstack-table/fields/tanstack-table-column-header.tsx +66 -0
  187. package/src/custom/tanstack-table/fields/tanstack-table-filter-date.tsx +180 -0
  188. package/src/custom/tanstack-table/fields/tanstack-table-filter-faceted.tsx +158 -0
  189. package/src/custom/tanstack-table/fields/tanstack-table-filter-text.tsx +76 -0
  190. package/src/custom/tanstack-table/fields/tanstack-table-pagination.tsx +136 -0
  191. package/src/custom/tanstack-table/fields/tanstack-table-plain-table.tsx +142 -0
  192. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-confirmation.tsx +77 -0
  193. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-custom-dialog.tsx +87 -0
  194. package/src/custom/tanstack-table/fields/tanstack-table-row-actions.tsx +151 -0
  195. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-custom-dialog.tsx +88 -0
  196. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-schemaform-dialog.tsx +47 -0
  197. package/src/custom/tanstack-table/fields/tanstack-table-toolbar.tsx +143 -0
  198. package/src/custom/tanstack-table/fields/tanstack-table-view-options.tsx +171 -0
  199. package/src/custom/tanstack-table/index.tsx +244 -0
  200. package/src/custom/tanstack-table/types/index.ts +328 -0
  201. package/src/custom/tanstack-table/utils/cell-with-actions.tsx +21 -0
  202. package/src/custom/tanstack-table/utils/column-names.ts +26 -0
  203. package/src/custom/tanstack-table/utils/columns-by-row-data.tsx +312 -0
  204. package/src/custom/tanstack-table/utils/editable-columns-by-row-data.tsx +219 -0
  205. package/src/custom/tanstack-table/utils/faceted-boolean-options.tsx +22 -0
  206. package/src/custom/tanstack-table/utils/index.tsx +10 -0
  207. package/src/custom/tanstack-table/utils/pinning-styles.ts +57 -0
  208. package/src/custom/tanstack-table/utils/table.tsx +83 -0
  209. package/src/custom/tanstack-table/utils/test-conditions.ts +17 -0
  210. package/src/custom/timeline.tsx +208 -0
  211. package/src/custom/tree.tsx +200 -0
  212. package/src/custom/tscanify/browser.ts +66 -0
  213. package/src/custom/tscanify/index.ts +51 -0
  214. package/src/custom/tscanify/tscanify-browser.ts +522 -0
  215. package/src/custom/tscanify/tscanify.ts +262 -0
  216. package/src/custom/tscanify/types.ts +22 -0
  217. package/src/custom/webcam.tsx +737 -0
  218. package/src/hooks/.gitkeep +0 -0
  219. package/src/hooks/use-callback-ref.ts +27 -0
  220. package/src/hooks/use-controllable-state.ts +67 -0
  221. package/src/hooks/use-debounce.ts +19 -0
  222. package/src/hooks/use-is-visible.ts +23 -0
  223. package/src/hooks/use-media-query.ts +21 -0
  224. package/src/hooks/use-mobile.ts +21 -0
  225. package/src/hooks/use-on-window-resize.ts +15 -0
  226. package/src/hooks/use-scroll.tsx +22 -0
  227. package/src/lib/utils.ts +61 -0
  228. package/src/lib/zod.ts +2 -0
  229. package/src/styles/core.css +57 -0
  230. package/src/styles/globals.css +130 -0
  231. package/src/test/email-input.test.tsx +217 -0
  232. package/src/test/password-input.test.tsx +92 -0
  233. package/src/test/select-tabs.test.tsx +302 -0
  234. package/src/test/selectable.test.tsx +1093 -0
  235. package/tsconfig.json +13 -0
  236. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { PasswordInput } from "../custom/password-input";
5
+
6
+ const getInput = () =>
7
+ document.querySelector(
8
+ 'input[type="password"], input[type="text"]'
9
+ ) as HTMLInputElement;
10
+
11
+ describe("PasswordInput", () => {
12
+ it("renders input and toggle button", () => {
13
+ render(<PasswordInput />);
14
+ expect(getInput()).toBeInTheDocument();
15
+ expect(screen.getByRole("button")).toBeInTheDocument();
16
+ });
17
+
18
+ it("shows password when toggle clicked", async () => {
19
+ render(<PasswordInput />);
20
+ const input = getInput();
21
+ const toggle = document.getElementById("toggle-password-visibility-button");
22
+ expect(input.type).toBe("password");
23
+ await userEvent.click(toggle!);
24
+ expect(input.type).toBe("text");
25
+ await userEvent.click(toggle!);
26
+ expect(input.type).toBe("password");
27
+ });
28
+
29
+ it("accepts value and onChange", async () => {
30
+ const handleChange = jest.fn();
31
+ render(<PasswordInput value="abc" onChange={handleChange} />);
32
+ const input = getInput();
33
+ expect(input.value).toBe("abc");
34
+ await userEvent.type(input, "123");
35
+ expect(handleChange).toHaveBeenCalled();
36
+ });
37
+
38
+ it("shows generator button if showGenerator", () => {
39
+ render(<PasswordInput showGenerator />);
40
+ expect(
41
+ document.getElementById("generate-password-button")
42
+ ).toBeInTheDocument();
43
+ });
44
+
45
+ it("generates password when generator clicked", async () => {
46
+ render(<PasswordInput showGenerator />);
47
+ const input = getInput();
48
+ const generator = document.getElementById("generate-password-button");
49
+ await userEvent.click(generator!);
50
+ await new Promise((r) => setTimeout(r, 100)); // Wait for async updates
51
+ expect(input.value.length).toBeGreaterThan(0);
52
+ });
53
+
54
+ it("forwards ref", () => {
55
+ const ref = React.createRef<HTMLInputElement>();
56
+ render(<PasswordInput ref={ref} />);
57
+ expect(ref.current).toBeInstanceOf(HTMLInputElement);
58
+ });
59
+
60
+ it("disables input and buttons when disabled", () => {
61
+ render(<PasswordInput disabled showGenerator />);
62
+ expect(getInput()).toBeDisabled();
63
+ expect(
64
+ document.getElementById("toggle-password-visibility-button")
65
+ ).toBeDisabled();
66
+ expect(document.getElementById("generate-password-button")).toBeDisabled();
67
+ });
68
+
69
+ it("generates password with correct length", async () => {
70
+ const passwordLength = 16;
71
+ render(<PasswordInput showGenerator passwordLength={passwordLength} />);
72
+ const input = getInput();
73
+ const generator = document.getElementById("generate-password-button");
74
+ await userEvent.click(generator!);
75
+ await new Promise((r) => setTimeout(r, 100));
76
+ expect(input.value.length).toBe(passwordLength);
77
+ });
78
+
79
+ it("generated password contains required character types", async () => {
80
+ render(<PasswordInput showGenerator />);
81
+ const input = getInput();
82
+ const generator = document.getElementById("generate-password-button");
83
+ await userEvent.click(generator!);
84
+ await new Promise((r) => setTimeout(r, 100));
85
+ const password = input.value;
86
+ // Check for lowercase, uppercase, numbers, and symbols
87
+ expect(/[a-z]/.test(password)).toBe(true);
88
+ expect(/[A-Z]/.test(password)).toBe(true);
89
+ expect(/[0-9]/.test(password)).toBe(true);
90
+ expect(/[!@#$%^&*()_+\-=[\]{}|;:,.<>?]/.test(password)).toBe(true);
91
+ });
92
+ });
@@ -0,0 +1,302 @@
1
+ import { render, screen, fireEvent } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import SelectTabs, { SelectTabsContent } from "../custom/select-tabs";
4
+
5
+ describe("SelectTabs", () => {
6
+ const renderSelectTabs = (props = {}) => {
7
+ const defaultProps = {
8
+ value: "",
9
+ onValueChange: jest.fn(),
10
+ ...props,
11
+ };
12
+
13
+ return render(
14
+ <SelectTabs {...defaultProps}>
15
+ <SelectTabsContent value="tab1">Tab 1</SelectTabsContent>
16
+ <SelectTabsContent value="tab2">Tab 2</SelectTabsContent>
17
+ <SelectTabsContent value="tab3">Tab 3</SelectTabsContent>
18
+ </SelectTabs>
19
+ );
20
+ };
21
+
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ describe("Basic Rendering", () => {
27
+ it("renders all tab contents", () => {
28
+ renderSelectTabs();
29
+
30
+ expect(screen.getByText("Tab 1")).toBeInTheDocument();
31
+ expect(screen.getByText("Tab 2")).toBeInTheDocument();
32
+ expect(screen.getByText("Tab 3")).toBeInTheDocument();
33
+ });
34
+
35
+ it("renders with custom className", () => {
36
+ const { container } = renderSelectTabs({ className: "custom-class" });
37
+
38
+ expect(container.firstChild).toHaveClass("custom-class");
39
+ });
40
+
41
+ it("renders unchecked icons for inactive tabs", () => {
42
+ renderSelectTabs();
43
+
44
+ const circleIcons = screen.getAllByTestId("lucide-circle");
45
+ expect(circleIcons).toHaveLength(3);
46
+ });
47
+ });
48
+
49
+ describe("Tab Selection", () => {
50
+ it("shows checked icon for active tab", () => {
51
+ renderSelectTabs({ value: "tab2" });
52
+
53
+ expect(screen.getByTestId("lucide-circle-check-big")).toBeInTheDocument();
54
+ expect(screen.getAllByTestId("lucide-circle")).toHaveLength(2);
55
+ });
56
+
57
+ it("applies active styles to selected tab", () => {
58
+ renderSelectTabs({ value: "tab1" });
59
+
60
+ const activeButton = screen.getByText("Tab 1").closest("button");
61
+ expect(activeButton).toHaveClass("border-primary/80", "text-primary/80");
62
+ });
63
+
64
+ it("applies default styles to inactive tabs", () => {
65
+ renderSelectTabs({ value: "tab1" });
66
+
67
+ const inactiveButton = screen.getByText("Tab 2").closest("button");
68
+ expect(inactiveButton).toHaveClass("border-gray-300", "text-gray-700/80");
69
+ });
70
+
71
+ it("calls onValueChange when tab is clicked", async () => {
72
+ const user = userEvent.setup();
73
+ const onValueChange = jest.fn();
74
+ renderSelectTabs({ onValueChange });
75
+
76
+ await user.click(screen.getByText("Tab 2"));
77
+
78
+ expect(onValueChange).toHaveBeenCalledWith("tab2");
79
+ expect(onValueChange).toHaveBeenCalledTimes(1);
80
+ });
81
+
82
+ it("changes active tab when different tab is clicked", async () => {
83
+ const user = userEvent.setup();
84
+ const onValueChange = jest.fn();
85
+ renderSelectTabs({ value: "tab1", onValueChange });
86
+
87
+ await user.click(screen.getByText("Tab 3"));
88
+
89
+ expect(onValueChange).toHaveBeenCalledWith("tab3");
90
+ });
91
+
92
+ it("does not call onValueChange when same tab is clicked and deselect is false", async () => {
93
+ const user = userEvent.setup();
94
+ const onValueChange = jest.fn();
95
+ renderSelectTabs({ value: "tab1", onValueChange, deselect: false });
96
+
97
+ await user.click(screen.getByText("Tab 1"));
98
+
99
+ expect(onValueChange).not.toHaveBeenCalled();
100
+ });
101
+ });
102
+
103
+ describe("Deselection Feature", () => {
104
+ it("deselects tab when same tab is clicked and deselect is true", async () => {
105
+ const user = userEvent.setup();
106
+ const onValueChange = jest.fn();
107
+ renderSelectTabs({ value: "tab1", onValueChange, deselect: true });
108
+
109
+ await user.click(screen.getByText("Tab 1"));
110
+
111
+ expect(onValueChange).toHaveBeenCalledWith("");
112
+ });
113
+
114
+ it("shows no checked icons when tab is deselected", async () => {
115
+ const user = userEvent.setup();
116
+ renderSelectTabs({ value: "tab1", deselect: true });
117
+
118
+ await user.click(screen.getByText("Tab 1"));
119
+
120
+ expect(
121
+ screen.queryByTestId("lucide-circle-check-big")
122
+ ).not.toBeInTheDocument();
123
+ expect(screen.getAllByTestId("lucide-circle")).toHaveLength(3);
124
+ });
125
+ });
126
+
127
+ describe("Disabled State", () => {
128
+ it("does not respond to clicks when disabled", async () => {
129
+ const user = userEvent.setup();
130
+ const onValueChange = jest.fn();
131
+ renderSelectTabs({ onValueChange, disabled: true });
132
+
133
+ await user.click(screen.getByText("Tab 1"));
134
+
135
+ expect(onValueChange).not.toHaveBeenCalled();
136
+ });
137
+
138
+ it("does not deselect when disabled and deselect is true", async () => {
139
+ const user = userEvent.setup();
140
+ const onValueChange = jest.fn();
141
+ renderSelectTabs({
142
+ value: "tab1",
143
+ onValueChange,
144
+ disabled: true,
145
+ deselect: true,
146
+ });
147
+
148
+ await user.click(screen.getByText("Tab 1"));
149
+
150
+ expect(onValueChange).not.toHaveBeenCalled();
151
+ });
152
+ });
153
+
154
+ describe("Edge Cases", () => {
155
+ it("handles empty value prop", () => {
156
+ renderSelectTabs({ value: "" });
157
+
158
+ expect(
159
+ screen.queryByTestId("lucide-circle-check-big")
160
+ ).not.toBeInTheDocument();
161
+ expect(screen.getAllByTestId("lucide-circle")).toHaveLength(3);
162
+ });
163
+
164
+ it("handles undefined onValueChange", async () => {
165
+ const user = userEvent.setup();
166
+ renderSelectTabs({ onValueChange: undefined });
167
+
168
+ // Should not throw error
169
+ await user.click(screen.getByText("Tab 1"));
170
+
171
+ expect(screen.getByTestId("lucide-circle-check-big")).toBeInTheDocument();
172
+ });
173
+
174
+ it("renders with no children", () => {
175
+ const { container } = render(<SelectTabs />);
176
+
177
+ expect(container.firstChild).toBeInTheDocument();
178
+ expect(container.firstChild).toHaveClass("w-full", "grid", "gap-3");
179
+ });
180
+
181
+ it("handles string children in SelectTabsContent", () => {
182
+ render(
183
+ <SelectTabs>
184
+ <SelectTabsContent value="string-test">
185
+ String Content
186
+ </SelectTabsContent>
187
+ </SelectTabs>
188
+ );
189
+
190
+ expect(screen.getByText("String Content")).toBeInTheDocument();
191
+ });
192
+
193
+ it("handles JSX children in SelectTabsContent", () => {
194
+ render(
195
+ <SelectTabs>
196
+ <SelectTabsContent value="jsx-test">
197
+ <div>JSX Content</div>
198
+ </SelectTabsContent>
199
+ </SelectTabs>
200
+ );
201
+
202
+ expect(screen.getByText("JSX Content")).toBeInTheDocument();
203
+ });
204
+ });
205
+
206
+ describe("Multiple Tab Scenarios", () => {
207
+ it("handles switching between multiple tabs", async () => {
208
+ const user = userEvent.setup();
209
+ const onValueChange = jest.fn();
210
+ renderSelectTabs({ onValueChange });
211
+
212
+ // Click tab 1
213
+ await user.click(screen.getByText("Tab 1"));
214
+ expect(onValueChange).toHaveBeenCalledWith("tab1");
215
+
216
+ // Click tab 3
217
+ await user.click(screen.getByText("Tab 3"));
218
+ expect(onValueChange).toHaveBeenCalledWith("tab3");
219
+
220
+ // Click tab 2
221
+ await user.click(screen.getByText("Tab 2"));
222
+ expect(onValueChange).toHaveBeenCalledWith("tab2");
223
+
224
+ expect(onValueChange).toHaveBeenCalledTimes(3);
225
+ });
226
+
227
+ it("maintains correct state across multiple interactions with deselect", async () => {
228
+ const user = userEvent.setup();
229
+ const onValueChange = jest.fn();
230
+ renderSelectTabs({ onValueChange, deselect: true });
231
+
232
+ // Select tab 1
233
+ await user.click(screen.getByText("Tab 1"));
234
+ expect(onValueChange).toHaveBeenCalledWith("tab1");
235
+
236
+ // Deselect tab 1
237
+ await user.click(screen.getByText("Tab 1"));
238
+ expect(onValueChange).toHaveBeenCalledWith("");
239
+
240
+ // Select tab 2
241
+ await user.click(screen.getByText("Tab 2"));
242
+ expect(onValueChange).toHaveBeenCalledWith("tab2");
243
+
244
+ expect(onValueChange).toHaveBeenCalledTimes(3);
245
+ });
246
+ });
247
+
248
+ describe("Button Properties", () => {
249
+ it("renders buttons with correct variant", () => {
250
+ renderSelectTabs();
251
+
252
+ const buttons = screen.getAllByRole("button");
253
+ buttons.forEach((button) => {
254
+ expect(button).toHaveClass(
255
+ "justify-between",
256
+ "border-2",
257
+ "gap-5",
258
+ "flex-1"
259
+ );
260
+ });
261
+ });
262
+
263
+ it("renders buttons as clickable elements", () => {
264
+ renderSelectTabs();
265
+
266
+ const buttons = screen.getAllByRole("button");
267
+ buttons.forEach((button) => {
268
+ expect(button.tagName).toBe("BUTTON");
269
+ });
270
+ });
271
+ });
272
+
273
+ describe("Accessibility", () => {
274
+ it("renders buttons that are keyboard accessible", async () => {
275
+ const user = userEvent.setup();
276
+ const onValueChange = jest.fn();
277
+ renderSelectTabs({ onValueChange });
278
+
279
+ const firstButton = screen.getByText("Tab 1").closest("button");
280
+
281
+ // Focus and press Enter
282
+ firstButton?.focus();
283
+ await user.keyboard("{Enter}");
284
+
285
+ expect(onValueChange).toHaveBeenCalledWith("tab1");
286
+ });
287
+
288
+ it("renders buttons that are accessible via Space key", async () => {
289
+ const user = userEvent.setup();
290
+ const onValueChange = jest.fn();
291
+ renderSelectTabs({ onValueChange });
292
+
293
+ const firstButton = screen.getByText("Tab 1").closest("button");
294
+
295
+ // Focus and press Space
296
+ firstButton?.focus();
297
+ await user.keyboard(" ");
298
+
299
+ expect(onValueChange).toHaveBeenCalledWith("tab1");
300
+ });
301
+ });
302
+ });