provider-batch-toolbar-selection.test.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * @vitest-environment happy-dom
  3. */
  4. import { act } from "react";
  5. import { createRoot } from "react-dom/client";
  6. import { describe, expect, it, vi } from "vitest";
  7. import type { ProviderDisplay, ProviderType } from "@/types/provider";
  8. // Mock next-intl
  9. vi.mock("next-intl", () => ({
  10. useTranslations: () => (key: string) => key,
  11. }));
  12. // Mock UI components
  13. vi.mock("@/components/ui/button", () => ({
  14. Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
  15. }));
  16. vi.mock("@/components/ui/checkbox", () => ({
  17. Checkbox: ({ checked, onCheckedChange, ...props }: any) => (
  18. <input
  19. type="checkbox"
  20. checked={checked}
  21. onChange={(e: any) => onCheckedChange?.(e.target.checked)}
  22. {...props}
  23. />
  24. ),
  25. }));
  26. vi.mock("@/components/ui/dropdown-menu", () => ({
  27. DropdownMenu: ({ children }: any) => <div data-testid="dropdown-menu">{children}</div>,
  28. DropdownMenuTrigger: ({ children }: any) => (
  29. <div data-testid="dropdown-menu-trigger">{children}</div>
  30. ),
  31. DropdownMenuContent: ({ children }: any) => (
  32. <div data-testid="dropdown-menu-content">{children}</div>
  33. ),
  34. DropdownMenuItem: ({ children, onClick, ...props }: any) => (
  35. <div data-testid="dropdown-menu-item" role="menuitem" onClick={onClick} {...props}>
  36. {children}
  37. </div>
  38. ),
  39. }));
  40. // Mock lucide-react
  41. vi.mock("lucide-react", () => ({
  42. ChevronDown: () => <span data-testid="chevron-down-icon" />,
  43. Pencil: () => <span data-testid="pencil-icon" />,
  44. X: () => <span data-testid="x-icon" />,
  45. }));
  46. function createProvider(
  47. id: number,
  48. providerType: ProviderType,
  49. groupTag: string | null = null
  50. ): ProviderDisplay {
  51. return { id, providerType, groupTag } as ProviderDisplay;
  52. }
  53. function render(node: React.ReactNode) {
  54. const container = document.createElement("div");
  55. document.body.appendChild(container);
  56. const root = createRoot(container);
  57. act(() => {
  58. root.render(node);
  59. });
  60. return {
  61. container,
  62. unmount: () => {
  63. act(() => root.unmount());
  64. container.remove();
  65. },
  66. };
  67. }
  68. // Import after mocks
  69. import { ProviderBatchToolbar } from "@/app/[locale]/settings/providers/_components/batch-edit/provider-batch-toolbar";
  70. const defaultProps = {
  71. isMultiSelectMode: false,
  72. allSelected: false,
  73. selectedCount: 0,
  74. totalCount: 5,
  75. onEnterMode: vi.fn(),
  76. onExitMode: vi.fn(),
  77. onSelectAll: vi.fn(),
  78. onInvertSelection: vi.fn(),
  79. onOpenBatchEdit: vi.fn(),
  80. providers: [] as ProviderDisplay[],
  81. onSelectByType: vi.fn(),
  82. onSelectByGroup: vi.fn(),
  83. };
  84. describe("ProviderBatchToolbar - Selection enhancements", () => {
  85. it("does NOT render type/group dropdowns when NOT in multi-select mode", () => {
  86. const providers = [createProvider(1, "claude"), createProvider(2, "openai-compatible")];
  87. const { container, unmount } = render(
  88. <ProviderBatchToolbar {...defaultProps} providers={providers} />
  89. );
  90. const dropdowns = container.querySelectorAll('[data-testid="dropdown-menu"]');
  91. expect(dropdowns.length).toBe(0);
  92. unmount();
  93. });
  94. it("renders Select by Type dropdown in multi-select mode when providers have multiple types", () => {
  95. const providers = [
  96. createProvider(1, "claude"),
  97. createProvider(2, "claude"),
  98. createProvider(3, "openai-compatible"),
  99. ];
  100. const { container, unmount } = render(
  101. <ProviderBatchToolbar {...defaultProps} isMultiSelectMode={true} providers={providers} />
  102. );
  103. const buttons = container.querySelectorAll("button");
  104. const typeButton = Array.from(buttons).find((b) => b.textContent?.includes("selectByType"));
  105. expect(typeButton).toBeTruthy();
  106. const items = container.querySelectorAll('[data-testid="dropdown-menu-item"]');
  107. const typeItems = Array.from(items).filter(
  108. (item) =>
  109. item.getAttribute("data-value") === "claude" ||
  110. item.getAttribute("data-value") === "openai-compatible"
  111. );
  112. expect(typeItems.length).toBe(2);
  113. unmount();
  114. });
  115. it("calls onSelectByType with correct type when clicking a type option", () => {
  116. const onSelectByType = vi.fn();
  117. const providers = [createProvider(1, "claude"), createProvider(2, "openai-compatible")];
  118. const { container, unmount } = render(
  119. <ProviderBatchToolbar
  120. {...defaultProps}
  121. isMultiSelectMode={true}
  122. providers={providers}
  123. onSelectByType={onSelectByType}
  124. />
  125. );
  126. const claudeItem = container.querySelector('[data-value="claude"]');
  127. expect(claudeItem).toBeTruthy();
  128. act(() => {
  129. claudeItem!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  130. });
  131. expect(onSelectByType).toHaveBeenCalledWith("claude");
  132. unmount();
  133. });
  134. it("renders Select by Group dropdown when providers have groups", () => {
  135. const providers = [
  136. createProvider(1, "claude", "production"),
  137. createProvider(2, "claude", "staging"),
  138. createProvider(3, "claude", "production"),
  139. ];
  140. const { container, unmount } = render(
  141. <ProviderBatchToolbar {...defaultProps} isMultiSelectMode={true} providers={providers} />
  142. );
  143. const buttons = container.querySelectorAll("button");
  144. const groupButton = Array.from(buttons).find((b) => b.textContent?.includes("selectByGroup"));
  145. expect(groupButton).toBeTruthy();
  146. const items = container.querySelectorAll('[data-testid="dropdown-menu-item"]');
  147. const groupItems = Array.from(items).filter(
  148. (item) =>
  149. item.getAttribute("data-value") === "production" ||
  150. item.getAttribute("data-value") === "staging"
  151. );
  152. expect(groupItems.length).toBe(2);
  153. unmount();
  154. });
  155. it("calls onSelectByGroup with correct group when clicking a group option", () => {
  156. const onSelectByGroup = vi.fn();
  157. const providers = [
  158. createProvider(1, "claude", "production"),
  159. createProvider(2, "claude", "staging"),
  160. ];
  161. const { container, unmount } = render(
  162. <ProviderBatchToolbar
  163. {...defaultProps}
  164. isMultiSelectMode={true}
  165. providers={providers}
  166. onSelectByGroup={onSelectByGroup}
  167. />
  168. );
  169. const productionItem = container.querySelector('[data-value="production"]');
  170. expect(productionItem).toBeTruthy();
  171. act(() => {
  172. productionItem!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  173. });
  174. expect(onSelectByGroup).toHaveBeenCalledWith("production");
  175. unmount();
  176. });
  177. it("does NOT render type dropdown when all filtered providers have same type", () => {
  178. const providers = [createProvider(1, "claude"), createProvider(2, "claude")];
  179. const { container, unmount } = render(
  180. <ProviderBatchToolbar {...defaultProps} isMultiSelectMode={true} providers={providers} />
  181. );
  182. const buttons = container.querySelectorAll("button");
  183. const typeButton = Array.from(buttons).find((b) => b.textContent?.includes("selectByType"));
  184. expect(typeButton).toBeFalsy();
  185. unmount();
  186. });
  187. it("does NOT render group dropdown when no groups exist", () => {
  188. const providers = [
  189. createProvider(1, "claude", null),
  190. createProvider(2, "openai-compatible", null),
  191. ];
  192. const { container, unmount } = render(
  193. <ProviderBatchToolbar {...defaultProps} isMultiSelectMode={true} providers={providers} />
  194. );
  195. const buttons = container.querySelectorAll("button");
  196. const groupButton = Array.from(buttons).find((b) => b.textContent?.includes("selectByGroup"));
  197. expect(groupButton).toBeFalsy();
  198. unmount();
  199. });
  200. });