Skip to content

Conversation

@coderabbitai
Copy link

@coderabbitai coderabbitai bot commented Dec 29, 2025

Unit test generation was requested by @borhanidev.

The following files were modified:

  • TESTING.md
  • TEST_README.md
  • run-tests.sh
  • src/renderer/src/__tests__/components/nav.test.jsx
  • src/renderer/src/__tests__/components/ui/dropdown.test.jsx
  • src/renderer/src/__tests__/setup.js
  • src/renderer/src/__tests__/store/sidebarStore.test.js
  • tweaks/__tests__/registry-validation.test.js
  • update-package-json.sh
  • vitest.config.js

@coderabbitai
Copy link
Author

coderabbitai bot commented Dec 29, 2025

Important

Review skipped

CodeRabbit bot authored PR detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@borhanidev
Copy link

@CodeRabbit Please generate unit tests for the functions below…

New Features

Collapsible sidebar navigation for optimized screen space
Improved dropdown positioning and interactions
UI Improvements

Redesigned navigation with relocated branding to sidebar header
Enhanced visual styling across components: modern rounded corners, blur effects, and refined hover animations
Streamlined title bar for better screen real estate
Updated form inputs and card designs with improved visual hierarchy and transitions
Refined theme selector and button styling

@coderabbitai
Copy link
Author

coderabbitai bot commented Dec 29, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of changed files in PR #60
gh pr view 60 --json files --jq '.files[].path' | grep -v '__tests__' | grep -v 'test\.' | grep -v 'TESTING.md' | grep -v 'TEST_README.md' | grep -v 'vitest.config.js' | grep -v 'run-tests.sh' | grep -v 'update-package-json.sh'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get the PR diff to understand what was changed
gh pr diff 60 --patch > /tmp/pr_diff.txt
# Show files that were changed (non-test files)
cat /tmp/pr_diff.txt | grep -E '^diff --git' | grep -v '__tests__' | grep -v 'test\.' | grep -v 'TESTING.md' | grep -v 'TEST_README.md' | grep -v 'vitest.config.js' | grep -v 'run-tests.sh' | grep -v 'update-package-json.sh'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for component files in the renderer src directory
fd -e jsx -e js -e ts -e tsx . src/renderer/src/components --type f

Length of output: 750


🏁 Script executed:

#!/bin/bash
# Check for sidebar-related files
fd sidebar src/renderer/src --type f

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check for dropdown-related files
fd dropdown src/renderer/src --type f

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Read the navigation component
cat src/renderer/src/components/nav.jsx

Length of output: 5850


🏁 Script executed:

#!/bin/bash
# Read the dropdown component
cat src/renderer/src/components/ui/dropdown.jsx

Length of output: 2076


🏁 Script executed:

#!/bin/bash
# Check if there's a sidebarStore file
fd sidebarStore src/renderer/src/store --type f 2>/dev/null || fd sidebarStore src/renderer/src --type f

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Read existing test files to understand the pattern
cat src/renderer/src/__tests__/components/nav.test.jsx

Length of output: 16077


🏁 Script executed:

#!/bin/bash
# Read the dropdown test
cat src/renderer/src/__tests__/components/ui/dropdown.test.jsx

Length of output: 13589


🏁 Script executed:

#!/bin/bash
# Read other UI components that were mentioned
cat src/renderer/src/components/ui/button.jsx

Length of output: 1452


🏁 Script executed:

#!/bin/bash
# Read card component
cat src/renderer/src/components/ui/card.jsx

Length of output: 344


🏁 Script executed:

#!/bin/bash
# Read input component
cat src/renderer/src/components/ui/input.jsx

Length of output: 1355


🏁 Script executed:

#!/bin/bash
# Read titlebar component
cat src/renderer/src/components/titlebar.jsx

Length of output: 1650


🏁 Script executed:

#!/bin/bash
# Search for store files
fd -e js -e jsx -e ts -e tsx . src/renderer/src/store --type f

Length of output: 75


🏁 Script executed:

#!/bin/bash
# Check if sidebarStore exists
cat src/renderer/src/store/sidebarStore.js 2>/dev/null || echo "File not found"

Length of output: 14


🏁 Script executed:

#!/bin/bash
# Look at the sidebarStore test to understand what it should do
cat src/renderer/src/__tests__/store/sidebarStore.test.js

Length of output: 7881


🏁 Script executed:

#!/bin/bash
# Check for other components that might need tests - theme selector
fd theme src/renderer/src/components --type f

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for all store files
ls -la src/renderer/src/store/ 2>/dev/null || echo "Store directory listing failed"

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Check the test setup file
cat src/renderer/src/__tests__/setup.js

Length of output: 950


@borhanidev I'll generate comprehensive unit tests for the components that don't have tests yet. I notice that the sidebarStore.js file is missing but has tests, so I'll create that first, then generate tests for the UI components.

1. Create the missing sidebarStore.js implementation

File: src/renderer/src/store/sidebarStore.js

import { create } from 'zustand'
import { persist } from 'zustand/middleware'

const useSidebarStore = create(
  persist(
    (set) => ({
      isCollapsed: false,
      toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
      setCollapsed: (collapsed) => set({ isCollapsed: collapsed }),
    }),
    {
      name: 'sidebar-storage',
    }
  )
)

export default useSidebarStore

2. Unit Tests for Button Component

File: src/renderer/src/__tests__/components/ui/button.test.jsx

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Button from '../../../components/ui/button'

describe('Button Component', () => {
  describe('Rendering', () => {
    it('should render button with children', () => {
      render(<Button>Click Me</Button>)
      expect(screen.getByRole('button')).toHaveTextContent('Click Me')
    })

    it('should render as button element by default', () => {
      render(<Button>Test</Button>)
      expect(screen.getByRole('button').tagName).toBe('BUTTON')
    })

    it('should render with custom element using as prop', () => {
      render(<Button as="div">Custom Element</Button>)
      const element = screen.getByText('Custom Element')
      expect(element.tagName).toBe('DIV')
    })

    it('should render children elements', () => {
      render(
        <Button>
          <span>Icon</span>
          <span>Text</span>
        </Button>
      )
      expect(screen.getByText('Icon')).toBeInTheDocument()
      expect(screen.getByText('Text')).toBeInTheDocument()
    })
  })

  describe('Variants', () => {
    it('should apply primary variant by default', () => {
      render(<Button>Primary</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-sparkle-primary', 'text-white')
    })

    it('should apply outline variant', () => {
      render(<Button variant="outline">Outline</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('border-sparkle-primary', 'text-sparkle-primary')
    })

    it('should apply secondary variant', () => {
      render(<Button variant="secondary">Secondary</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-sparkle-card', 'text-sparkle-text')
    })

    it('should apply danger variant', () => {
      render(<Button variant="danger">Danger</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-red-600', 'text-white')
    })

    it('should handle invalid variant gracefully', () => {
      render(<Button variant="invalid">Test</Button>)
      expect(screen.getByRole('button')).toBeInTheDocument()
    })
  })

  describe('Sizes', () => {
    it('should apply small size by default', () => {
      render(<Button>Small</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('px-3', 'py-1.5', 'text-sm')
    })

    it('should apply medium size', () => {
      render(<Button size="md">Medium</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('px-4', 'py-2', 'text-base')
    })

    it('should apply large size', () => {
      render(<Button size="lg">Large</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('px-5', 'py-3', 'text-lg')
    })
  })

  describe('Disabled State', () => {
    it('should not be disabled by default', () => {
      render(<Button>Enabled</Button>)
      expect(screen.getByRole('button')).not.toBeDisabled()
    })

    it('should apply disabled attribute when disabled', () => {
      render(<Button disabled>Disabled</Button>)
      expect(screen.getByRole('button')).toBeDisabled()
    })

    it('should apply disabled styling when disabled', () => {
      render(<Button disabled>Disabled</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('opacity-50', 'cursor-not-allowed', 'pointer-events-none')
    })

    it('should not trigger onClick when disabled', async () => {
      const handleClick = vi.fn()
      render(<Button disabled onClick={handleClick}>Disabled</Button>)
      
      const button = screen.getByRole('button')
      await userEvent.click(button)
      
      expect(handleClick).not.toHaveBeenCalled()
    })
  })

  describe('Click Handling', () => {
    it('should call onClick when clicked', async () => {
      const handleClick = vi.fn()
      render(<Button onClick={handleClick}>Click Me</Button>)
      
      await userEvent.click(screen.getByRole('button'))
      
      expect(handleClick).toHaveBeenCalledTimes(1)
    })

    it('should handle multiple clicks', async () => {
      const handleClick = vi.fn()
      render(<Button onClick={handleClick}>Click Me</Button>)
      
      const button = screen.getByRole('button')
      await userEvent.click(button)
      await userEvent.click(button)
      await userEvent.click(button)
      
      expect(handleClick).toHaveBeenCalledTimes(3)
    })

    it('should work without onClick handler', async () => {
      render(<Button>No Handler</Button>)
      
      const button = screen.getByRole('button')
      await userEvent.click(button)
      
      expect(button).toBeInTheDocument()
    })
  })

  describe('Custom Props', () => {
    it('should apply custom className', () => {
      render(<Button className="custom-class">Custom</Button>)
      expect(screen.getByRole('button')).toHaveClass('custom-class')
    })

    it('should merge custom className with base classes', () => {
      render(<Button className="extra-class">Test</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('extra-class')
      expect(button).toHaveClass('rounded-lg')
    })

    it('should forward additional props', () => {
      render(<Button data-testid="custom-button" aria-label="Test Button">Test</Button>)
      
      const button = screen.getByTestId('custom-button')
      expect(button).toHaveAttribute('aria-label', 'Test Button')
    })

    it('should support type attribute', () => {
      render(<Button type="submit">Submit</Button>)
      expect(screen.getByRole('button')).toHaveAttribute('type', 'submit')
    })
  })

  describe('Styling and Animations', () => {
    it('should have base styling classes', () => {
      render(<Button>Test</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass(
        'flex',
        'items-center',
        'rounded-lg',
        'font-medium',
        'transition-all',
        'duration-200'
      )
    })

    it('should have active scale animation class', () => {
      render(<Button>Test</Button>)
      expect(screen.getByRole('button')).toHaveClass('active:scale-90')
    })

    it('should have focus outline hidden', () => {
      render(<Button>Test</Button>)
      expect(screen.getByRole('button')).toHaveClass('focus:outline-hidden')
    })

    it('should be non-selectable', () => {
      render(<Button>Test</Button>)
      expect(screen.getByRole('button')).toHaveClass('select-none')
    })
  })

  describe('Keyboard Accessibility', () => {
    it('should trigger onClick with Enter key', async () => {
      const handleClick = vi.fn()
      render(<Button onClick={handleClick}>Test</Button>)
      
      const button = screen.getByRole('button')
      button.focus()
      await userEvent.keyboard('{Enter}')
      
      expect(handleClick).toHaveBeenCalled()
    })

    it('should trigger onClick with Space key', async () => {
      const handleClick = vi.fn()
      render(<Button onClick={handleClick}>Test</Button>)
      
      const button = screen.getByRole('button')
      button.focus()
      await userEvent.keyboard(' ')
      
      expect(handleClick).toHaveBeenCalled()
    })

    it('should be focusable', () => {
      render(<Button>Test</Button>)
      const button = screen.getByRole('button')
      button.focus()
      expect(button).toHaveFocus()
    })

    it('should not be focusable when disabled', () => {
      render(<Button disabled>Test</Button>)
      const button = screen.getByRole('button')
      button.focus()
      expect(button).not.toHaveFocus()
    })
  })

  describe('Complex Content', () => {
    it('should render with icons and text', () => {
      render(
        <Button>
          <svg data-testid="icon" />
          <span>Button Text</span>
        </Button>
      )
      
      expect(screen.getByTestId('icon')).toBeInTheDocument()
      expect(screen.getByText('Button Text')).toBeInTheDocument()
    })

    it('should handle empty children', () => {
      render(<Button>{''}</Button>)
      expect(screen.getByRole('button')).toBeInTheDocument()
    })

    it('should render with null children', () => {
      render(<Button>{null}</Button>)
      expect(screen.getByRole('button')).toBeInTheDocument()
    })
  })

  describe('Edge Cases', () => {
    it('should handle rapid clicks', async () => {
      const handleClick = vi.fn()
      render(<Button onClick={handleClick}>Rapid Click</Button>)
      
      const button = screen.getByRole('button')
      
      for (let i = 0; i < 10; i++) {
        await userEvent.click(button)
      }
      
      expect(handleClick).toHaveBeenCalledTimes(10)
    })

    it('should handle very long text', () => {
      const longText = 'A'.repeat(1000)
      render(<Button>{longText}</Button>)
      expect(screen.getByRole('button')).toHaveTextContent(longText)
    })

    it('should handle special characters', () => {
      render(<Button>{'<>&"\'Test'}</Button>)
      expect(screen.getByRole('button')).toHaveTextContent('<>&"\'Test')
    })

    it('should maintain class order consistency', () => {
      const { container, rerender } = render(<Button>Test</Button>)
      const initialClasses = container.firstChild?.className
      
      rerender(<Button>Test Updated</Button>)
      const updatedClasses = container.firstChild?.className
      
      expect(initialClasses).toBe(updatedClasses)
    })
  })

  describe('Variant and Size Combinations', () => {
    it('should combine primary variant with large size', () => {
      render(<Button variant="primary" size="lg">Large Primary</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-sparkle-primary', 'px-5', 'py-3')
    })

    it('should combine outline variant with small size', () => {
      render(<Button variant="outline" size="sm">Small Outline</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('border-sparkle-primary', 'px-3', 'py-1.5')
    })

    it('should combine danger variant with medium size', () => {
      render(<Button variant="danger" size="md">Medium Danger</Button>)
      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-red-600', 'px-4', 'py-2')
    })
  })
})

3. Unit Tests for Card Component

File: src/renderer/src/__tests__/components/ui/card.test.jsx

import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Card from '../../../components/ui/card'

describe('Card Component', () => {
  describe('Rendering', () => {
    it('should render card with children', () => {
      render(<Card>Card Content</Card>)
      expect(screen.getByText('Card Content')).toBeInTheDocument()
    })

    it('should render as div element', () => {
      const { container } = render(<Card>Test</Card>)
      expect(container.firstChild?.tagName).toBe('DIV')
    })

    it('should render multiple children', () => {
      render(
        <Card>
          <h2>Title</h2>
          <p>Description</p>
        </Card>
      )
      expect(screen.getByText('Title')).toBeInTheDocument()
      expect(screen.getByText('Description')).toBeInTheDocument()
    })

    it('should render nested components', () => {
      render(
        <Card>
          <div>
            <span>Nested Content</span>
          </div>
        </Card>
      )
      expect(screen.getByText('Nested Content')).toBeInTheDocument()
    })
  })

  describe('Styling', () => {
    it('should have base styling classes', () => {
      const { container } = render(<Card>Test</Card>)
      const card = container.firstChild
      
      expect(card).toHaveClass('bg-sparkle-card')
      expect(card).toHaveClass('border')
      expect(card).toHaveClass('border-sparkle-border')
      expect(card).toHaveClass('rounded-xl')
    })

    it('should have hover border effect', () => {
      const { container } = render(<Card>Test</Card>)
      expect(container.firstChild).toHaveClass('hover:border-sparkle-primary')
    })

    it('should have transition class', () => {
      const { container } = render(<Card>Test</Card>)
      expect(container.firstChild).toHaveClass('transition')
    })

    it('should have group class for nested hover effects', () => {
      const { container } = render(<Card>Test</Card>)
      expect(container.firstChild).toHaveClass('group')
    })
  })

  describe('Custom Props', () => {
    it('should apply custom className', () => {
      const { container } = render(<Card className="custom-class">Test</Card>)
      expect(container.firstChild).toHaveClass('custom-class')
    })

    it('should merge custom className with base classes', () => {
      const { container } = render(<Card className="extra-padding">Test</Card>)
      const card = container.firstChild
      
      expect(card).toHaveClass('extra-padding')
      expect(card).toHaveClass('bg-sparkle-card')
      expect(card).toHaveClass('rounded-xl')
    })

    it('should forward additional props', () => {
      const { container } = render(
        <Card data-testid="custom-card" aria-label="Test Card">
          Test
        </Card>
      )
      
      const card = screen.getByTestId('custom-card')
      expect(card).toHaveAttribute('aria-label', 'Test Card')
    })

    it('should handle onClick events', async () => {
      const handleClick = vi.fn()
      render(<Card onClick={handleClick}>Clickable Card</Card>)
      
      await userEvent.click(screen.getByText('Clickable Card'))
      
      expect(handleClick).toHaveBeenCalledTimes(1)
    })

    it('should support id attribute', () => {
      const { container } = render(<Card id="test-card">Test</Card>)
      expect(container.firstChild).toHaveAttribute('id', 'test-card')
    })
  })

  describe('Content Variations', () => {
    it('should handle empty children', () => {
      const { container } = render(<Card>{''}</Card>)
      expect(container.firstChild).toBeInTheDocument()
    })

    it('should handle null children', () => {
      const { container } = render(<Card>{null}</Card>)
      expect(container.firstChild).toBeInTheDocument()
    })

    it('should handle undefined children', () => {
      const { container } = render(<Card>{undefined}</Card>)
      expect(container.firstChild).toBeInTheDocument()
    })

    it('should render with text content', () => {
      render(<Card>Simple text content</Card>)
      expect(screen.getByText('Simple text content')).toBeInTheDocument()
    })

    it('should render with mixed content types', () => {
      render(
        <Card>
          Text
          <span>Span</span>
          {123}
          {true && <div>Conditional</div>}
        </Card>
      )
      
      expect(screen.getByText('Text')).toBeInTheDocument()
      expect(screen.getByText('Span')).toBeInTheDocument()
      expect(screen.getByText('123')).toBeInTheDocument()
      expect(screen.getByText('Conditional')).toBeInTheDocument()
    })
  })

  describe('Layout and Structure', () => {
    it('should maintain block-level display', () => {
      const { container } = render(<Card>Test</Card>)
      const card = container.firstChild as HTMLElement
      const styles = window.getComputedStyle(card)
      
      expect(card).toBeInTheDocument()
    })

    it('should support custom padding through className', () => {
      const { container } = render(<Card className="p-6">Test</Card>)
      expect(container.firstChild).toHaveClass('p-6')
    })

    it('should support custom margin through className', () => {
      const { container } = render(<Card className="m-4">Test</Card>)
      expect(container.firstChild).toHaveClass('m-4')
    })
  })

  describe('Interactive Features', () => {
    it('should handle hover events', async () => {
      const handleMouseEnter = vi.fn()
      const handleMouseLeave = vi.fn()
      
      render(
        <Card onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
          Hoverable Card
        </Card>
      )
      
      const card = screen.getByText('Hoverable Card')
      
      await userEvent.hover(card)
      expect(handleMouseEnter).toHaveBeenCalled()
      
      await userEvent.unhover(card)
      expect(handleMouseLeave).toHaveBeenCalled()
    })

    it('should handle focus events', () => {
      const handleFocus = vi.fn()
      render(
        <Card tabIndex={0} onFocus={handleFocus}>
          Focusable Card
        </Card>
      )
      
      const card = screen.getByText('Focusable Card')
      card.focus()
      
      expect(handleFocus).toHaveBeenCalled()
    })
  })

  describe('Edge Cases', () => {
    it('should handle very long content', () => {
      const longContent = 'A'.repeat(10000)
      render(<Card>{longContent}</Card>)
      expect(screen.getByText(longContent)).toBeInTheDocument()
    })

    it('should handle special characters', () => {
      render(<Card>{'<>&"\' Special'}</Card>)
      expect(screen.getByText('<>&"\' Special')).toBeInTheDocument()
    })

    it('should handle deeply nested content', () => {
      render(
        <Card>
          <div>
            <div>
              <div>
                <div>
                  <span>Deep Content</span>
                </div>
              </div>
            </div>
          </div>
        </Card>
      )
      expect(screen.getByText('Deep Content')).toBeInTheDocument()
    })

    it('should support multiple cards in a container', () => {
      render(
        <div>
          <Card>Card 1</Card>
          <Card>Card 2</Card>
          <Card>Card 3</Card>
        </div>
      )
      
      expect(screen.getByText('Card 1')).toBeInTheDocument()
      expect(screen.getByText('Card 2')).toBeInTheDocument()
      expect(screen.getByText('Card 3')).toBeInTheDocument()
    })
  })

  describe('ClassName Merging', () => {
    it('should properly merge conflicting classes', () => {
      const { container } = render(
        <Card className="bg-red-500 border-blue-500">Test</Card>
      )
      const card = container.firstChild
      
      // Custom classes should be applied
      expect(card).toHaveClass('bg-red-500')
      expect(card).toHaveClass('border-blue-500')
    })

    it('should handle multiple custom classes', () => {
      const { container } = render(
        <Card className="shadow-lg hover:shadow-xl p-4 m-2">Test</Card>
      )
      const card = container.firstChild
      
      expect(card).toHaveClass('shadow-lg')
      expect(card).toHaveClass('hover:shadow-xl')
      expect(card).toHaveClass('p-4')
      expect(card).toHaveClass('m-2')
    })
  })

  describe('Accessibility', () => {
    it('should support ARIA attributes', () => {
      const { container } = render(
        <Card role="article" aria-labelledby="card-title">
          <h2 id="card-title">Card Title</h2>
          <p>Card content</p>
        </Card>
      )
      
      const card = container.firstChild
      expect(card).toHaveAttribute('role', 'article')
      expect(card).toHaveAttribute('aria-labelledby', 'card-title')
    })

    it('should support custom roles', () => {
      const { container } = render(<Card role="region">Test</Card>)
      expect(container.firstChild).toHaveAttribute('role', 'region')
    })
  })

  describe('Rerender Behavior', () => {
    it('should update content on rerender', () => {
      const { rerender } = render(<Card>Initial Content</Card>)
      expect(screen.getByText('Initial Content')).toBeInTheDocument()
      
      rerender(<Card>Updated Content</Card>)
      expect(screen.getByText('Updated Content')).toBeInTheDocument()
      expect(screen.queryByText('Initial Content')).not.toBeInTheDocument()
    })

    it('should maintain className on rerender', () => {
      const { container, rerender } = render(<Card className="custom">Test</Card>)
      expect(container.firstChild).toHaveClass('custom')
      
      rerender(<Card className="custom">Updated</Card>)
      expect(container.firstChild).toHaveClass('custom')
    })
  })
})

4. Unit Tests for Input Component

File: src/renderer/src/__tests__/components/ui/input.test.jsx

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Input, LargeInput } from '../../../components/ui/input'
import { Search } from 'lucide-react'

describe('Input Component', () => {
  describe('Rendering', () => {
    it('should render input element', () => {
      render(<Input />)
      expect(screen.getByRole('textbox')).toBeInTheDocument()
    })

    it('should render with placeholder', () => {
      render(<Input placeholder="Enter text" />)
      expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument()
    })

    it('should render with default value', () => {
      render(<Input defaultValue="Default text" />)
      expect(screen.getByRole('textbox')).toHaveValue('Default text')
    })

    it('should support different input types', () => {
      const { rerender } = render(<Input type="text" />)
      expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text')
      
      rerender(<Input type="email" />)
      expect(screen.getByRole('textbox')).toHaveAttribute('type', 'email')
      
      rerender(<Input type="password" />)
      const passwordInput = document.querySelector('input[type="password"]')
      expect(passwordInput).toBeInTheDocument()
    })
  })

  describe('Styling', () => {
    it('should have base styling classes', () => {
      render(<Input />)
      const input = screen.getByRole('textbox')
      
      expect(input).toHaveClass('w-full')
      expect(input).toHaveClass('bg-sparkle-card')
      expect(input).toHaveClass('border')
      expect(input).toHaveClass('border-sparkle-border')
      expect(input).toHaveClass('rounded-lg')
      expect(input).toHaveClass('px-3')
      expect(input).toHaveClass('py-2')
    })

    it('should have focus styles', () => {
      render(<Input />)
      const input = screen.getByRole('textbox')
      
      expect(input).toHaveClass('focus:ring-0')
      expect(input).toHaveClass('focus:outline-hidden')
      expect(input).toHaveClass('focus:border-sparkle-primary')
    })

    it('should apply custom className', () => {
      render(<Input className="custom-class" />)
      expect(screen.getByRole('textbox')).toHaveClass('custom-class')
    })

    it('should merge custom className with base classes', () => {
      render(<Input className="extra-padding" />)
      const input = screen.getByRole('textbox')
      
      expect(input).toHaveClass('extra-padding')
      expect(input).toHaveClass('w-full')
      expect(input).toHaveClass('rounded-lg')
    })
  })

  describe('User Interaction', () => {
    it('should handle text input', async () => {
      const user = userEvent.setup()
      render(<Input />)
      const input = screen.getByRole('textbox')
      
      await user.type(input, 'Hello World')
      
      expect(input).toHaveValue('Hello World')
    })

    it('should call onChange when value changes', async () => {
      const handleChange = vi.fn()
      const user = userEvent.setup()
      render(<Input onChange={handleChange} />)
      
      const input = screen.getByRole('textbox')
      await user.type(input, 'Test')
      
      expect(handleChange).toHaveBeenCalled()
    })

    it('should handle clearing input', async () => {
      const user = userEvent.setup()
      render(<Input defaultValue="Initial" />)
      const input = screen.getByRole('textbox')
      
      await user.clear(input)
      
      expect(input).toHaveValue('')
    })

    it('should handle focus and blur', () => {
      const handleFocus = vi.fn()
      const handleBlur = vi.fn()
      render(<Input onFocus={handleFocus} onBlur={handleBlur} />)
      
      const input = screen.getByRole('textbox')
      
      input.focus()
      expect(handleFocus).toHaveBeenCalled()
      
      input.blur()
      expect(handleBlur).toHaveBeenCalled()
    })
  })

  describe('Props Forwarding', () => {
    it('should forward additional props', () => {
      render(<Input data-testid="custom-input" aria-label="Test Input" />)
      
      const input = screen.getByTestId('custom-input')
      expect(input).toHaveAttribute('aria-label', 'Test Input')
    })

    it('should support disabled attribute', () => {
      render(<Input disabled />)
      expect(screen.getByRole('textbox')).toBeDisabled()
    })

    it('should support readOnly attribute', () => {
      render(<Input readOnly />)
      expect(screen.getByRole('textbox')).toHaveAttribute('readOnly')
    })

    it('should support required attribute', () => {
      render(<Input required />)
      expect(screen.getByRole('textbox')).toBeRequired()
    })

    it('should support maxLength attribute', () => {
      render(<Input maxLength={10} />)
      expect(screen.getByRole('textbox')).toHaveAttribute('maxLength', '10')
    })
  })

  describe('Edge Cases', () => {
    it('should handle very long text input', async () => {
      const user = userEvent.setup()
      const longText = 'A'.repeat(1000)
      render(<Input />)
      
      const input = screen.getByRole('textbox')
      await user.type(input, longText)
      
      expect(input).toHaveValue(longText)
    })

    it('should handle special characters', async () => {
      const user = userEvent.setup()
      render(<Input />)
      
      const input = screen.getByRole('textbox')
      await user.type(input, '<>&"\'')
      
      expect(input.value).toContain('<>&"\'')
    })

    it('should handle rapid input changes', async () => {
      const handleChange = vi.fn()
      const user = userEvent.setup()
      render(<Input onChange={handleChange} />)
      
      const input = screen.getByRole('textbox')
      await user.type(input, 'Quick typing test')
      
      expect(handleChange).toHaveBeenCalled()
    })
  })
})

describe('LargeInput Component', () => {
  describe('Rendering', () => {
    it('should render input within container', () => {
      render(<LargeInput />)
      expect(screen.getByRole('textbox')).toBeInTheDocument()
    })

    it('should render with placeholder', () => {
      render(<LargeInput placeholder="Search here" />)
      expect(screen.getByPlaceholderText('Search here')).toBeInTheDocument()
    })

    it('should render with icon', () => {
      const { container } = render(<LargeInput icon={Search} />)
      const svg = container.querySelector('svg')
      expect(svg).toBeInTheDocument()
    })

    it('should render without icon', () => {
      const { container } = render(<LargeInput />)
      const svg = container.querySelector('svg')
      expect(svg).not.toBeInTheDocument()
    })

    it('should render with value', () => {
      render(<LargeInput value="Test value" onChange={() => {}} />)
      expect(screen.getByRole('textbox')).toHaveValue('Test value')
    })
  })

  describe('Styling', () => {
    it('should have container styling classes', () => {
      const { container } = render(<LargeInput />)
      const wrapper = container.firstChild
      
      expect(wrapper).toHaveClass('flex')
      expect(wrapper).toHaveClass('items-center')
      expect(wrapper).toHaveClass('gap-3')
      expect(wrapper).toHaveClass('bg-sparkle-card')
      expect(wrapper).toHaveClass('border')
      expect(wrapper).toHaveClass('border-sparkle-border')
      expect(wrapper).toHaveClass('rounded-xl')
    })

    it('should have focus-within border effect', () => {
      const { container } = render(<LargeInput />)
      expect(container.firstChild).toHaveClass('focus-within:border-sparkle-primary')
    })

    it('should have backdrop blur effect', () => {
      const { container } = render(<LargeInput />)
      expect(container.firstChild).toHaveClass('backdrop-blur-xs')
    })

    it('should apply custom className to container', () => {
      const { container } = render(<LargeInput className="custom-container" />)
      expect(container.firstChild).toHaveClass('custom-container')
    })

    it('should have transparent input background', () => {
      render(<LargeInput />)
      const input = screen.getByRole('textbox')
      expect(input).toHaveClass('bg-transparent')
    })

    it('should have proper padding', () => {
      render(<LargeInput />)
      const input = screen.getByRole('textbox')
      expect(input).toHaveClass('py-3', 'px-0')
    })
  })

  describe('Icon Rendering', () => {
    it('should render icon with correct styling', () => {
      const { container } = render(<LargeInput icon={Search} />)
      const svg = container.querySelector('svg')
      
      expect(svg).toHaveClass('w-5')
      expect(svg).toHaveClass('h-5')
      expect(svg).toHaveClass('text-sparkle-text-secondary')
    })

    it('should position icon before input', () => {
      const { container } = render(<LargeInput icon={Search} value="test" onChange={() => {}} />)
      const wrapper = container.firstChild
      const children = Array.from(wrapper?.children || [])
      
      expect(children[0]?.tagName).toBe('svg')
      expect(children[1]?.tagName).toBe('INPUT')
    })
  })

  describe('User Interaction', () => {
    it('should handle controlled input', async () => {
      const handleChange = vi.fn()
      const user = userEvent.setup()
      
      const { rerender } = render(
        <LargeInput value="" onChange={handleChange} />
      )
      
      const input = screen.getByRole('textbox')
      await user.type(input, 'Test')
      
      expect(handleChange).toHaveBeenCalled()
    })

    it('should handle focus', () => {
      render(<LargeInput />)
      const input = screen.getByRole('textbox')
      
      input.focus()
      expect(input).toHaveFocus()
    })

    it('should handle blur', () => {
      const handleBlur = vi.fn()
      render(<LargeInput onBlur={handleBlur} />)
      
      const input = screen.getByRole('textbox')
      input.focus()
      input.blur()
      
      expect(handleBlur).toHaveBeenCalled()
    })

    it('should call onChange on input', async () => {
      const handleChange = vi.fn()
      const user = userEvent.setup()
      render(<LargeInput value="" onChange={handleChange} />)
      
      const input = screen.getByRole('textbox')
      await user.type(input, 'New text')
      
      expect(handleChange).toHaveBeenCalled()
    })
  })

  describe('Props Forwarding', () => {
    it('should forward additional props to input', () => {
      render(
        <LargeInput
          data-testid="large-input"
          aria-label="Large Input"
        />
      )
      
      const input = screen.getByTestId('large-input')
      expect(input).toHaveAttribute('aria-label', 'Large Input')
    })

    it('should support disabled state', () => {
      render(<LargeInput disabled />)
      expect(screen.getByRole('textbox')).toBeDisabled()
    })

    it('should support readOnly state', () => {
      render(<LargeInput readOnly />)
      expect(screen.getByRole('textbox')).toHaveAttribute('readOnly')
    })
  })

  describe('Placeholder Styling', () => {
    it('should have placeholder with secondary text color', () => {
      render(<LargeInput placeholder="Test placeholder" />)
      const input = screen.getByPlaceholderText('Test placeholder')
      expect(input).toHaveClass('placeholder:text-sparkle-text-secondary')
    })
  })

  describe('Edge Cases', () => {
    it('should handle empty value', () => {
      render(<LargeInput value="" onChange={() => {}} />)
      expect(screen.getByRole('textbox')).toHaveValue('')
    })

    it('should handle undefined icon gracefully', () => {
      const { container } = render(<LargeInput icon={undefined} />)
      const svg = container.querySelector('svg')
      expect(svg).not.toBeInTheDocument()
    })

    it('should handle long text input', async () => {
      const handleChange = vi.fn()
      const user = userEvent.setup()
      render(<LargeInput value="" onChange={handleChange} />)
      
      const input = screen.getByRole('textbox')
      const longText = 'A'.repeat(100)
      await user.type(input, longText)
      
      expect(handleChange).toHaveBeenCalled()
    })

    it('should handle special characters in placeholder', () => {
      render(<LargeInput placeholder="<>&\"' Special" />)
      expect(screen.getByPlaceholderText('<>&"\' Special')).toBeInTheDocument()
    })
  })

  describe('Container Interactions', () => {
    it('should focus input when container is clicked', () => {
      const { container } = render(<LargeInput />)
      const wrapper = container.firstChild as HTMLElement
      const input = screen.getByRole('textbox')
      
      fireEvent.click(wrapper)
      expect(input).toHaveFocus()
    })
  })

  describe('Multiple Instances', () => {
    it('should render multiple LargeInput components independently', () => {
      render(
        <div>
          <LargeInput placeholder="Input 1" value="Value 1" onChange={() => {}} />
          <LargeInput placeholder="Input 2" value="Value 2" onChange={() => {}} />
        </div>
      )
      
      expect(screen.getByPlaceholderText('Input 1')).toHaveValue('Value 1')
      expect(screen.getByPlaceholderText('Input 2')).toHaveValue('Value 2')
    })
  })
})

5. Unit Tests for TitleBar Component

File: src/renderer/src/__tests__/components/titlebar.test.jsx

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import TitleBar from '../../components/titlebar'
import { close, minimize, toggleMaximize } from '../../lib/electron'

// Mock electron functions
vi.mock('../../lib/electron', () => ({
  close: vi.fn(),
  minimize: vi.fn(),
  toggleMaximize: vi.fn()
}))

describe('TitleBar Component', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('Rendering', () => {
    it('should render the title bar', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toBeInTheDocument()
    })

    it('should render Sparkle logo', () => {
      render(<TitleBar />)
      const logo = screen.getByAltText('Sparkle')
      expect(logo).toBeInTheDocument()
    })

    it('should render Sparkle title', () => {
      render(<TitleBar />)
      expect(screen.getByText('Sparkle')).toBeInTheDocument()
    })

    it('should render Beta badge', () => {
      render(<TitleBar />)
      expect(screen.getByText('Beta')).toBeInTheDocument()
    })

    it('should render all window control buttons', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      expect(buttons).toHaveLength(3) // Minimize, Maximize, Close
    })
  })

  describe('Window Controls', () => {
    it('should render minimize button', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      expect(buttons[0]).toBeInTheDocument()
    })

    it('should render maximize button', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      expect(buttons[1]).toBeInTheDocument()
    })

    it('should render close button', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      expect(buttons[2]).toBeInTheDocument()
    })

    it('should call minimize when minimize button is clicked', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      await user.click(buttons[0])
      
      expect(minimize).toHaveBeenCalledTimes(1)
    })

    it('should call toggleMaximize when maximize button is clicked', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      await user.click(buttons[1])
      
      expect(toggleMaximize).toHaveBeenCalledTimes(1)
    })

    it('should call close when close button is clicked', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      await user.click(buttons[2])
      
      expect(close).toHaveBeenCalledTimes(1)
    })
  })

  describe('Styling and Layout', () => {
    it('should have fixed positioning', () => {
      const { container } = render(<TitleBar />)
      const titleBar = container.firstChild
      
      expect(titleBar).toHaveClass('fixed', 'top-0', 'left-0', 'right-0')
    })

    it('should have correct height', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('h-[50px]')
    })

    it('should have correct z-index', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('z-50')
    })

    it('should have draggable region style', () => {
      const { container } = render(<TitleBar />)
      const titleBar = container.firstChild as HTMLElement
      expect(titleBar.style.WebkitAppRegion).toBe('drag')
    })

    it('should have non-draggable region for controls', () => {
      const { container } = render(<TitleBar />)
      const controlsContainer = container.querySelector('[style*="no-drag"]')
      expect(controlsContainer).toBeInTheDocument()
    })

    it('should use flex layout', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('flex', 'justify-between', 'items-center')
    })

    it('should have background color', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('bg-sparkle-bg')
    })
  })

  describe('Logo and Branding', () => {
    it('should render logo with correct size', () => {
      render(<TitleBar />)
      const logo = screen.getByAltText('Sparkle')
      expect(logo).toHaveClass('h-5', 'w-5')
    })

    it('should render title with correct styling', () => {
      render(<TitleBar />)
      const title = screen.getByText('Sparkle')
      expect(title).toHaveClass('text-sparkle-text', 'text-sm', 'font-medium')
    })

    it('should group logo and title together', () => {
      const { container } = render(<TitleBar />)
      const brandingContainer = container.querySelector('.flex.items-center.gap-3')
      expect(brandingContainer).toBeInTheDocument()
    })
  })

  describe('Beta Badge', () => {
    it('should render Beta badge with correct styling', () => {
      render(<TitleBar />)
      const badge = screen.getByText('Beta')
      expect(badge).toHaveClass('bg-sparkle-card')
      expect(badge).toHaveClass('border')
      expect(badge).toHaveClass('border-sparkle-border-secondary')
      expect(badge).toHaveClass('rounded-xl')
    })

    it('should have correct badge dimensions', () => {
      render(<TitleBar />)
      const badge = screen.getByText('Beta')
      expect(badge).toHaveClass('w-16', 'text-center', 'text-sm')
    })
  })

  describe('Button Icons', () => {
    it('should render Minus icon for minimize button', () => {
      const { container } = render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const minusIcon = buttons[0].querySelector('svg')
      expect(minusIcon).toBeInTheDocument()
    })

    it('should render Square icon for maximize button', () => {
      const { container } = render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const squareIcon = buttons[1].querySelector('svg')
      expect(squareIcon).toBeInTheDocument()
    })

    it('should render X icon for close button', () => {
      const { container } = render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const xIcon = buttons[2].querySelector('svg')
      expect(xIcon).toBeInTheDocument()
    })

    it('should have correct icon sizes', () => {
      const { container } = render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      
      // Check minimize icon (Minus)
      const minusIcon = buttons[0].querySelector('svg')
      expect(minusIcon).toHaveAttribute('width', '16')
      expect(minusIcon).toHaveAttribute('height', '16')
      
      // Check close icon (X)
      const xIcon = buttons[2].querySelector('svg')
      expect(xIcon).toHaveAttribute('width', '16')
      expect(xIcon).toHaveAttribute('height', '16')
    })
  })

  describe('Button Styling', () => {
    it('should style minimize button correctly', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const minimizeButton = buttons[0]
      
      expect(minimizeButton).toHaveClass('h-[50px]', 'w-12')
      expect(minimizeButton).toHaveClass('text-sparkle-text-secondary')
      expect(minimizeButton).toHaveClass('hover:bg-sparkle-accent')
    })

    it('should style maximize button correctly', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const maximizeButton = buttons[1]
      
      expect(maximizeButton).toHaveClass('h-[50px]', 'w-12')
      expect(maximizeButton).toHaveClass('text-sparkle-text-secondary')
      expect(maximizeButton).toHaveClass('hover:bg-sparkle-accent')
    })

    it('should style close button with danger color', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      const closeButton = buttons[2]
      
      expect(closeButton).toHaveClass('hover:bg-red-600')
      expect(closeButton).toHaveClass('hover:text-white')
    })

    it('should have transition classes on buttons', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      
      buttons.forEach(button => {
        expect(button).toHaveClass('transition-colors')
      })
    })
  })

  describe('Interactions', () => {
    it('should handle multiple minimize clicks', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      const minimizeButton = buttons[0]
      
      await user.click(minimizeButton)
      await user.click(minimizeButton)
      await user.click(minimizeButton)
      
      expect(minimize).toHaveBeenCalledTimes(3)
    })

    it('should handle multiple maximize clicks', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      const maximizeButton = buttons[1]
      
      await user.click(maximizeButton)
      await user.click(maximizeButton)
      
      expect(toggleMaximize).toHaveBeenCalledTimes(2)
    })

    it('should handle rapid button clicks', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      
      for (let i = 0; i < 10; i++) {
        await user.click(buttons[0])
      }
      
      expect(minimize).toHaveBeenCalledTimes(10)
    })
  })

  describe('Keyboard Accessibility', () => {
    it('should allow keyboard navigation between buttons', () => {
      render(<TitleBar />)
      const buttons = screen.getAllByRole('button')
      
      buttons[0].focus()
      expect(buttons[0]).toHaveFocus()
    })

    it('should trigger minimize with Enter key', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      buttons[0].focus()
      await user.keyboard('{Enter}')
      
      expect(minimize).toHaveBeenCalled()
    })

    it('should trigger close with Enter key', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      buttons[2].focus()
      await user.keyboard('{Enter}')
      
      expect(close).toHaveBeenCalled()
    })
  })

  describe('Edge Cases', () => {
    it('should not break when electron functions throw errors', async () => {
      minimize.mockImplementation(() => {
        throw new Error('Test error')
      })
      
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      
      await expect(async () => {
        await user.click(buttons[0])
      }).rejects.toThrow()
    })

    it('should handle rapid sequential button clicks', async () => {
      const user = userEvent.setup()
      render(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      
      await user.click(buttons[0])
      await user.click(buttons[1])
      await user.click(buttons[2])
      
      expect(minimize).toHaveBeenCalledTimes(1)
      expect(toggleMaximize).toHaveBeenCalledTimes(1)
      expect(close).toHaveBeenCalledTimes(1)
    })
  })

  describe('Visual Hierarchy', () => {
    it('should center items vertically', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('items-center')
    })

    it('should space elements correctly', () => {
      const { container } = render(<TitleBar />)
      const brandingContainer = container.querySelector('.gap-3')
      expect(brandingContainer).toBeInTheDocument()
    })

    it('should have proper padding', () => {
      const { container } = render(<TitleBar />)
      expect(container.firstChild).toHaveClass('pl-4')
    })
  })

  describe('Rerender Behavior', () => {
    it('should maintain state on rerender', () => {
      const { rerender } = render(<TitleBar />)
      expect(screen.getByText('Sparkle')).toBeInTheDocument()
      
      rerender(<TitleBar />)
      expect(screen.getByText('Sparkle')).toBeInTheDocument()
    })

    it('should not lose event handlers on rerender', async () => {
      const user = userEvent.setup()
      const { rerender } = render(<TitleBar />)
      
      rerender(<TitleBar />)
      
      const buttons = screen.getAllByRole('button')
      await user.click(buttons[0])
      
      expect(minimize).toHaveBeenCalled()
    })
  })
})

These comprehensive test suites cover:

  • Collapsible sidebar: Tests already exist in nav.test.jsx and sidebarStore.test.js
  • Dropdown interactions: Tests already exist in dropdown.test.jsx
  • UI Components: New tests for Button, Card, Input (both variants), and TitleBar
  • Visual styling: Hover states, transitions, rounded corners, blur effects
  • Form inputs: Comprehensive testing of Input and LargeInput components
  • Theme and styling: CSS class assertions for modern design elements

All tests follow the established patterns using Vitest and React Testing Library, with comprehensive coverage of edge cases, accessibility, and user interactions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants