mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30:15 +01:00
refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids, rename enums to PascalCase refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids chore: move sharing related components to dedicated "Sharing" directory chore: remove PublicSharingToggle component and update index exports chore: move non-sidepanel agent components to `~/components/Agents` chore: move AgentCategoryDisplay component with tests chore: remove commented out code refactor: change PERMISSION_BITS from const to enum for better type safety refactor: reorganize imports in GenericGrantAccessDialog and update index exports for hooks refactor: update type definitions to use ACCESS_ROLE_IDS for improved type safety refactor: remove unused canAccessPromptResource middleware and related code refactor: remove unused prompt access roles from createAccessRoleMethods refactor: update resourceType in AclEntry type definition to remove unused 'prompt' value refactor: introduce ResourceType enum and update resourceType usage across data provider files for improved type safety refactor: update resourceType usage to ResourceType enum across sharing and permissions components for improved type safety refactor: standardize resourceType usage to ResourceType enum across agent and prompt models, permissions controller, and middleware for enhanced type safety refactor: update resourceType references from PROMPT_GROUP to PROMPTGROUP for consistency across models, middleware, and components refactor: standardize access role IDs and resource type usage across agent, file, and prompt models for improved type safety and consistency chore: add typedefs for TUpdateResourcePermissionsRequest and TUpdateResourcePermissionsResponse to enhance type definitions chore: move SearchPicker to PeoplePicker dir refactor: implement debouncing for query changes in SearchPicker for improved performance chore: fix typing, import order for agent admin settings fix: agent admin settings, prevent agent form submission refactor: rename `ACCESS_ROLE_IDS` to `AccessRoleIds` refactor: replace PermissionBits with PERMISSION_BITS refactor: replace PERMISSION_BITS with PermissionBits
230 lines
6.4 KiB
TypeScript
230 lines
6.4 KiB
TypeScript
import React from 'react';
|
|
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import '@testing-library/jest-dom';
|
|
import CategoryTabs from '../CategoryTabs';
|
|
import type t from 'librechat-data-provider';
|
|
|
|
// Mock useLocalize hook
|
|
jest.mock('~/hooks/useLocalize', () => () => (key: string) => {
|
|
const mockTranslations: Record<string, string> = {
|
|
com_agents_top_picks: 'Top Picks',
|
|
com_agents_all: 'All',
|
|
com_ui_no_categories: 'No categories available',
|
|
com_agents_category_tabs_label: 'Agent Categories',
|
|
com_ui_agent_category_general: 'General',
|
|
com_ui_agent_category_hr: 'HR',
|
|
com_ui_agent_category_rd: 'R&D',
|
|
com_ui_agent_category_finance: 'Finance',
|
|
com_ui_agent_category_it: 'IT',
|
|
com_ui_agent_category_sales: 'Sales',
|
|
com_ui_agent_category_aftersales: 'After Sales',
|
|
};
|
|
return mockTranslations[key] || key;
|
|
});
|
|
|
|
describe('CategoryTabs', () => {
|
|
const mockCategories: t.TMarketplaceCategory[] = [
|
|
{ value: 'promoted', label: 'Top Picks', description: 'Our recommended agents', count: 5 },
|
|
{ value: 'all', label: 'All', description: 'All available agents', count: 20 },
|
|
{ value: 'general', label: 'General', description: 'General purpose agents', count: 8 },
|
|
{ value: 'hr', label: 'HR', description: 'HR agents', count: 3 },
|
|
{ value: 'finance', label: 'Finance', description: 'Finance agents', count: 4 },
|
|
];
|
|
|
|
const mockOnChange = jest.fn();
|
|
const user = userEvent.setup();
|
|
|
|
beforeEach(() => {
|
|
mockOnChange.mockClear();
|
|
});
|
|
|
|
it('renders provided categories', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
// Check for provided categories
|
|
expect(screen.getByText('Top Picks')).toBeInTheDocument();
|
|
expect(screen.getByText('All')).toBeInTheDocument();
|
|
expect(screen.getByText('General')).toBeInTheDocument();
|
|
expect(screen.getByText('HR')).toBeInTheDocument();
|
|
expect(screen.getByText('Finance')).toBeInTheDocument();
|
|
});
|
|
|
|
it('handles loading state properly', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={[]}
|
|
activeTab="promoted"
|
|
isLoading={true}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
// SmartLoader should handle loading behavior correctly
|
|
// The component should render without crashing during loading
|
|
expect(screen.queryByText('No categories available')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('highlights the active tab', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="general"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const generalTab = screen.getByText('General').closest('button');
|
|
expect(generalTab).toHaveClass('bg-surface-tertiary');
|
|
|
|
// Should have active underline
|
|
const underline = generalTab?.querySelector('.absolute.bottom-0');
|
|
expect(underline).toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onChange when a tab is clicked', async () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const hrTab = screen.getByText('HR');
|
|
await user.click(hrTab);
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith('hr');
|
|
});
|
|
|
|
it('handles promoted tab click correctly', async () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="general"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const topPicksTab = screen.getByText('Top Picks');
|
|
await user.click(topPicksTab);
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith('promoted');
|
|
});
|
|
|
|
it('handles all tab click correctly', async () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const allTab = screen.getByText('All');
|
|
await user.click(allTab);
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith('all');
|
|
});
|
|
|
|
it('shows inactive state for non-selected tabs', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const generalTab = screen.getByText('General').closest('button');
|
|
expect(generalTab).toHaveClass('bg-surface-secondary');
|
|
expect(generalTab).toHaveClass('text-text-secondary');
|
|
|
|
// Should not have active underline
|
|
const underline = generalTab?.querySelector('.absolute.bottom-0');
|
|
expect(underline).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('renders with proper accessibility', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const tabs = screen.getAllByRole('tab');
|
|
expect(tabs.length).toBe(5);
|
|
// Verify all tabs are properly clickable buttons
|
|
tabs.forEach((tab) => {
|
|
expect(tab.tagName).toBe('BUTTON');
|
|
});
|
|
});
|
|
|
|
it('handles keyboard navigation', async () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const generalTab = screen.getByText('General').closest('button')!;
|
|
|
|
// Focus the button and click it
|
|
generalTab.focus();
|
|
expect(document.activeElement).toBe(generalTab);
|
|
|
|
await user.click(generalTab);
|
|
expect(mockOnChange).toHaveBeenCalledWith('general');
|
|
});
|
|
|
|
it('shows empty state when categories prop is empty', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={[]}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
// Should show empty state message (localized)
|
|
expect(screen.getByText('No categories available')).toBeInTheDocument();
|
|
});
|
|
|
|
it('maintains consistent ordering of categories', () => {
|
|
render(
|
|
<CategoryTabs
|
|
categories={mockCategories}
|
|
activeTab="promoted"
|
|
isLoading={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
);
|
|
|
|
const tabs = screen.getAllByRole('tab');
|
|
const tabTexts = tabs.map((tab) => tab.textContent);
|
|
|
|
// Check that promoted is first and all is second
|
|
expect(tabTexts[0]).toBe('Top Picks');
|
|
expect(tabTexts[1]).toBe('All');
|
|
expect(tabTexts.length).toBe(5);
|
|
});
|
|
});
|