Files
archon/archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
Wirasm 1a78a8e287 feat: TanStack Query Migration Phase 2 - Cleanup and Test Reorganization (#588)
* refactor: migrate layouts to TanStack Query and Radix UI patterns

- Created new modern layout components in src/components/layout/
- Migrated from old MainLayout/SideNavigation to new system
- Added BackendStatus component with proper separation of concerns
- Fixed horizontal scrollbar issues in project list
- Renamed old layouts folder to agent-chat for unused chat panel
- Added layout directory to Biome configuration
- Fixed all linting and TypeScript issues in new layout code
- Uses TanStack Query for backend health monitoring
- Temporarily imports old settings/credentials until full migration

* test: reorganize test infrastructure with colocated tests in subdirectories

- Move tests into dedicated tests/ subdirectories within each feature
- Create centralized test utilities in src/features/testing/
- Update all import paths to match new structure
- Configure tsconfig.prod.json to exclude test files
- Remove legacy test files from old test/ directory
- All 32 tests passing with proper provider wrapping

* fix: use error boundary wrapper for ProjectPage

- Export ProjectsViewWithBoundary from projects feature module
- Update ProjectPage to use boundary-wrapped version
- Provides proper error containment and recovery with TanStack Query integration

* cleanup: remove unused MCP client components

- Remove ToolTestingPanel, ClientCard, and MCPClients components
- These were part of an unimplemented MCP clients feature
- Clean up commented import in MCPPage
- Preparing for proper MCP feature migration to features directory

* cleanup: remove unused mcpService.ts

- Remove duplicate/unused mcpService.ts (579 lines)
- Keep mcpServerService.ts which is actively used by MCPPage and useMCPQueries
- mcpService was never imported or used anywhere in the codebase

* cleanup: remove unused mcpClientService and update deprecation comments

- Remove mcpClientService.ts (445 lines) - no longer used after removing MCP client components
- Update deprecation comments in mcpServerService to remove references to deleted service
- This completes the MCP service cleanup

* fix: correct test directory exclusion in coverage config

Update coverage exclusion from 'test/' to 'tests/' to match actual
project structure and ensure proper test file exclusion from coverage.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: fix ArchonChatPanel import path in agent-chat.mdx

Update import from deprecated layouts to agent-chat directory.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: improve backend health hook and types

- Use existing ETag infrastructure in useBackendHealth for 70% bandwidth reduction
- Honor React Query cancellation signals with proper timeout handling
- Remove duplicate HealthResponse interface, import from shared types
- Add React type import to fix potential strict TypeScript issues

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: remove .d.ts exclusion from production TypeScript config

Removing **/*.d.ts exclusion to fix import.meta.env type errors in
production builds. The exclusion was preventing src/env.d.ts from
being included, breaking ImportMetaEnv interface definitions.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: implement modern MCP feature architecture

- Add new /features/mcp with TanStack Query integration
- Components: McpClientList, McpStatusBar, McpConfigSection
- Services: mcpApi with ETag caching
- Hooks: useMcpStatus, useMcpConfig, useMcpClients, useMcpSessionInfo
- Views: McpView with error boundary wrapper
- Full TypeScript types for MCP protocol

Part of TanStack Query migration phase 2.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: complete MCP modernization and cleanup

- Remove deprecated mcpServerService.ts (237 lines)
- Remove unused useMCPQueries.ts hooks (77 lines)
- Simplify MCPPage.tsx to use new feature architecture
- Export useSmartPolling from ui/hooks for MCP feature
- Add Python MCP API routes for backend integration

This completes the MCP migration to TanStack Query with:
- ETag caching for 70% bandwidth reduction
- Smart polling with visibility awareness
- Vertical slice architecture
- Full TypeScript type safety

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: correct MCP transport mode display and complete cleanup

- Fix backend API to return correct "streamable-http" transport mode
- Update frontend to dynamically display transport type from config
- Remove unused MCP functions (startMCPServer, stopMCPServer, getMCPServerStatus)
- Clean up unused MCPServerResponse interface
- Update log messages to show accurate transport mode
- Complete aggressive MCP cleanup with 75% code reduction (617 lines removed)

Backend changes:
- python/src/server/api_routes/mcp_api.py: Fix transport and logs
- Reduced from 818 to 201 lines while preserving all functionality

Frontend changes:
- McpStatusBar: Dynamic transport display based on config
- McpView: Pass config to status bar component
- api.ts: Remove unused MCP management functions

All MCP tools tested and verified working after cleanup.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* simplify MCP API to status-only endpoints

- Remove Docker container management functionality
- Remove start/stop/restart endpoints
- Simplify to status and config endpoints only
- Container is now managed entirely via docker-compose

* feat: complete MCP feature migration to TanStack Query

- Add MCP feature with TanStack Query hooks and services
- Create useMcpQueries hook with smart polling for status/config
- Implement mcpApi service with streamable-http transport
- Add MCP page component with real-time updates
- Export MCP hooks from features/ui for global access
- Fix logging bug in mcp_api.py (invalid error kwarg)
- Update docker command to v2 syntax (docker compose)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: clean up unused CSS and unify Tron-themed scrollbars

- Remove 200+ lines of unused CSS classes (62% file size reduction)
- Delete unused: glass classes, neon-dividers, card animations, screensaver animations
- Remove unused knowledge-item-card and hide-scrollbar styles
- Remove unused flip-card and card expansion animations
- Update scrollbar-thin to match Tron theme with blue glow effects
- Add gradient and glow effects to thin scrollbars for consistency
- Keep only actively used styles: neon-grid, scrollbars, animation delays

File reduced from 11.2KB to 4.3KB with no visual regressions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address CodeRabbit CSS review feedback

- Fix neon-grid Tailwind @apply with arbitrary values (breaking build)
- Convert hardcoded RGBA colors to HSL tokens using --blue-accent
- Add prefers-reduced-motion accessibility support
- Add Firefox dark mode scrollbar-color support
- Optimize transitions to specific properties instead of 'all'

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: properly close Docker client to prevent resource leak

- Add finally block to ensure Docker client is closed
- Prevents resource leak in get_container_status function
- Fix linting issues (whitespace and newline)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-06 13:43:53 +03:00

189 lines
5.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useSmartPolling } from '../useSmartPolling';
describe('useSmartPolling', () => {
beforeEach(() => {
// Reset document visibility state
Object.defineProperty(document, 'visibilityState', {
value: 'visible',
writable: true,
configurable: true,
});
Object.defineProperty(document, 'hidden', {
value: false,
writable: true,
configurable: true,
});
// Mock document.hasFocus
document.hasFocus = vi.fn(() => true);
});
afterEach(() => {
vi.clearAllTimers();
vi.clearAllMocks();
});
it('should return the base interval when document is visible and focused', () => {
const { result } = renderHook(() => useSmartPolling(5000));
expect(result.current.refetchInterval).toBe(5000);
expect(result.current.isActive).toBe(true);
expect(result.current.isVisible).toBe(true);
expect(result.current.hasFocus).toBe(true);
});
it('should disable polling when document is hidden', () => {
const { result } = renderHook(() => useSmartPolling(5000));
// Initially should be active
expect(result.current.isActive).toBe(true);
expect(result.current.refetchInterval).toBe(5000);
// Simulate tab becoming hidden
act(() => {
Object.defineProperty(document, 'hidden', {
value: true,
writable: true,
configurable: true,
});
document.dispatchEvent(new Event('visibilitychange'));
});
// Should be disabled (returns false)
expect(result.current.isVisible).toBe(false);
expect(result.current.isActive).toBe(false);
expect(result.current.refetchInterval).toBe(false);
});
it('should resume polling when document becomes visible again', () => {
const { result } = renderHook(() => useSmartPolling(5000));
// Make hidden
act(() => {
Object.defineProperty(document, 'hidden', {
value: true,
writable: true,
configurable: true,
});
document.dispatchEvent(new Event('visibilitychange'));
});
expect(result.current.refetchInterval).toBe(false);
// Make visible again
act(() => {
Object.defineProperty(document, 'hidden', {
value: false,
writable: true,
configurable: true,
});
document.dispatchEvent(new Event('visibilitychange'));
});
expect(result.current.isVisible).toBe(true);
expect(result.current.isActive).toBe(true);
expect(result.current.refetchInterval).toBe(5000);
});
it('should slow down to 60 seconds when window loses focus', () => {
const { result } = renderHook(() => useSmartPolling(5000));
// Initially focused
expect(result.current.refetchInterval).toBe(5000);
expect(result.current.hasFocus).toBe(true);
// Simulate window blur
act(() => {
window.dispatchEvent(new Event('blur'));
});
// Should be slowed down to 60 seconds
expect(result.current.hasFocus).toBe(false);
expect(result.current.isActive).toBe(false);
expect(result.current.refetchInterval).toBe(60000);
});
it('should resume normal speed when window regains focus', () => {
const { result } = renderHook(() => useSmartPolling(5000));
// Blur window
act(() => {
window.dispatchEvent(new Event('blur'));
});
expect(result.current.refetchInterval).toBe(60000);
// Focus window again
act(() => {
window.dispatchEvent(new Event('focus'));
});
expect(result.current.hasFocus).toBe(true);
expect(result.current.isActive).toBe(true);
expect(result.current.refetchInterval).toBe(5000);
});
it('should handle different base intervals', () => {
const { result: result1 } = renderHook(() => useSmartPolling(1000));
const { result: result2 } = renderHook(() => useSmartPolling(10000));
expect(result1.current.refetchInterval).toBe(1000);
expect(result2.current.refetchInterval).toBe(10000);
// When blurred, both should be 60 seconds
act(() => {
window.dispatchEvent(new Event('blur'));
});
expect(result1.current.refetchInterval).toBe(60000);
expect(result2.current.refetchInterval).toBe(60000);
});
it('should use default interval of 10000ms when not specified', () => {
const { result } = renderHook(() => useSmartPolling());
expect(result.current.refetchInterval).toBe(10000);
});
it('should cleanup event listeners on unmount', () => {
const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener');
const windowRemoveEventListenerSpy = vi.spyOn(window, 'removeEventListener');
const { unmount } = renderHook(() => useSmartPolling(5000));
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function));
expect(windowRemoveEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
expect(windowRemoveEventListenerSpy).toHaveBeenCalledWith('blur', expect.any(Function));
removeEventListenerSpy.mockRestore();
windowRemoveEventListenerSpy.mockRestore();
});
it('should correctly report isActive state', () => {
const { result } = renderHook(() => useSmartPolling(5000));
// Active when both visible and focused
expect(result.current.isActive).toBe(true);
// Not active when not focused
act(() => {
window.dispatchEvent(new Event('blur'));
});
expect(result.current.isActive).toBe(false);
// Not active when hidden
act(() => {
window.dispatchEvent(new Event('focus')); // Focus first
Object.defineProperty(document, 'hidden', {
value: true,
writable: true,
configurable: true,
});
document.dispatchEvent(new Event('visibilitychange'));
});
expect(result.current.isActive).toBe(false);
});
});