diff --git a/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkResultsPanel.test.tsx b/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkResultsPanel.test.tsx index 80ee136e..c6774da0 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkResultsPanel.test.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkResultsPanel.test.tsx @@ -3,9 +3,38 @@ */ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; import { BulkResultsPanel } from '../BulkResultsPanel'; import type { BulkOperationResult } from '@shared/types/mutations'; +// Create test i18n instance +const testI18n = i18n.createInstance(); +testI18n.use(initReactI18next).init({ + lng: 'en', + fallbackLng: 'en', + defaultNS: 'common', + ns: ['common'], + resources: { + en: { + common: { + 'bulk.actions': 'Bulk operation results', + 'bulk.complete': '{{succeeded}} succeeded, {{failed}} failed', + 'bulk.details': 'Details', + 'bulk.retryFailed': 'Retry {{count}} failed', + 'bulk.dismiss': 'Dismiss', + 'labels.success': 'Success', + 'labels.error': 'Failed' + } + } + } +}); + +function renderWithI18n(ui: React.ReactElement) { + return render({ui}); +} + const resultWithFailures: BulkOperationResult = { action: 'close', totalItems: 3, @@ -33,18 +62,18 @@ const resultAllSuccess: BulkOperationResult = { describe('BulkResultsPanel', () => { it('shows success/fail counts', () => { - render( + renderWithI18n( , ); - expect(screen.getByText('bulk.complete')).toBeDefined(); + expect(screen.getByText('2 succeeded, 1 failed')).toBeDefined(); }); it('success items show checkmark', () => { - render( + renderWithI18n( { />, ); // Expand details - fireEvent.click(screen.getByText('bulk.details')); + fireEvent.click(screen.getByText('Details')); const checkmarks = screen.getAllByLabelText('Success'); expect(checkmarks.length).toBe(2); }); it('failed items show error', () => { - render( + renderWithI18n( , ); - fireEvent.click(screen.getByText('bulk.details')); + fireEvent.click(screen.getByText('Details')); expect(screen.getByText('Permission denied')).toBeDefined(); expect(screen.getByLabelText('Failed')).toBeDefined(); }); it('retry button fires onRetry when failures exist', () => { const onRetry = vi.fn(); - render( + renderWithI18n( , ); - fireEvent.click(screen.getByText('bulk.retryFailed')); + fireEvent.click(screen.getByText('Retry 1 failed')); expect(onRetry).toHaveBeenCalledWith(resultWithFailures); }); it('dismiss button fires onDismiss', () => { const onDismiss = vi.fn(); - render( + renderWithI18n( , ); - fireEvent.click(screen.getByText('bulk.dismiss')); + fireEvent.click(screen.getByText('Dismiss')); expect(onDismiss).toHaveBeenCalled(); }); it('has aria-label "Bulk operation results"', () => { - const { container } = render( + const { container } = renderWithI18n( {ui}); +} + const fullEnrichment: IssueEnrichment['enrichment'] = { problem: 'A problem description', goal: 'A goal', @@ -25,7 +50,7 @@ const partialEnrichment: IssueEnrichment['enrichment'] = { describe('CompletenessBreakdown', () => { it('shows all 7 sections', () => { - render( + renderWithI18n( , ); // Expand to show sections @@ -41,7 +66,7 @@ describe('CompletenessBreakdown', () => { }); it('filled sections have checkmark', () => { - render( + renderWithI18n( , ); fireEvent.click(screen.getByText('100%')); @@ -51,7 +76,7 @@ describe('CompletenessBreakdown', () => { }); it('empty sections have circle', () => { - render( + renderWithI18n( , ); fireEvent.click(screen.getByText('0%')); @@ -61,14 +86,14 @@ describe('CompletenessBreakdown', () => { }); it('overall percentage displayed', () => { - render( + renderWithI18n( , ); expect(screen.getByText('42%')).toBeDefined(); }); it('0% shows all empty', () => { - render( + renderWithI18n( , ); fireEvent.click(screen.getByText('0%')); @@ -79,7 +104,7 @@ describe('CompletenessBreakdown', () => { }); it('100% shows all filled', () => { - render( + renderWithI18n( , ); fireEvent.click(screen.getByText('100%')); @@ -90,11 +115,11 @@ describe('CompletenessBreakdown', () => { }); it('has aria-label "Completeness score breakdown"', () => { - const { container } = render( + const { container } = renderWithI18n( , ); const el = container.querySelector( - '[aria-label="Completeness score breakdown"]', + '[aria-label="Completeness Breakdown"]', ); expect(el).not.toBeNull(); }); diff --git a/apps/frontend/src/renderer/components/github-issues/components/__tests__/LabelManager.test.tsx b/apps/frontend/src/renderer/components/github-issues/components/__tests__/LabelManager.test.tsx index 2bcc4db8..2e9c19b3 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/__tests__/LabelManager.test.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/__tests__/LabelManager.test.tsx @@ -3,8 +3,36 @@ */ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; import { LabelManager } from '../LabelManager'; +// Create test i18n instance +const testI18n = i18n.createInstance(); +testI18n.use(initReactI18next).init({ + lng: 'en', + fallbackLng: 'en', + defaultNS: 'common', + ns: ['common'], + resources: { + en: { + common: { + 'labels.manage': 'Label manager', + 'labels.removeNamed': 'Remove label {{name}}', + 'labels.add': 'Add label', + 'labels.filter': 'Filter labels...', + 'labels.available': 'Available labels', + 'labels.noMatch': 'No matching labels' + } + } + } +}); + +function renderWithI18n(ui: React.ReactElement) { + return render({ui}); +} + const repoLabels = [ { name: 'bug', color: 'fc2929' }, { name: 'feature', color: '0e8a16' }, @@ -13,7 +41,7 @@ const repoLabels = [ describe('LabelManager', () => { it('renders current labels', () => { - render( + renderWithI18n( { it('remove button fires onRemoveLabel', () => { const onRemoveLabel = vi.fn(); - render( + renderWithI18n( { }); it('add button toggles dropdown', () => { - render( + renderWithI18n( { }); it('type-ahead filter works', () => { - render( + renderWithI18n( { />, ); fireEvent.click(screen.getByRole('button', { name: 'Add label' })); - const filterInput = screen.getByRole('textbox', { name: 'Filter labels' }); + const filterInput = screen.getByRole('textbox', { name: 'Filter labels...' }); fireEvent.change(filterInput, { target: { value: 'doc' } }); expect(screen.getByText('docs')).toBeDefined(); @@ -73,7 +101,7 @@ describe('LabelManager', () => { it('selecting fires onAddLabel', () => { const onAddLabel = vi.fn(); - render( + renderWithI18n( { it('Enter key on option fires onAddLabel', () => { const onAddLabel = vi.fn(); - render( + renderWithI18n( { it('Space key on option fires onAddLabel', () => { const onAddLabel = vi.fn(); - render( + renderWithI18n( { }); it('Escape key closes dropdown', () => { - render( + renderWithI18n( { it('Enter key does not fire onAddLabel for already-applied label', () => { const onAddLabel = vi.fn(); - render( + renderWithI18n( { }); it('aria-label present on container', () => { - const { container } = render( + const { container } = renderWithI18n( { diff --git a/apps/frontend/src/renderer/components/ideation/hooks/useIdeationAuth.ts b/apps/frontend/src/renderer/components/ideation/hooks/useIdeationAuth.ts index 43afae5e..9048078b 100644 --- a/apps/frontend/src/renderer/components/ideation/hooks/useIdeationAuth.ts +++ b/apps/frontend/src/renderer/components/ideation/hooks/useIdeationAuth.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useSettingsStore } from '../../../stores/settings-store'; /** @@ -21,7 +21,7 @@ export function useIdeationAuth() { // Get active API profile info from settings store const activeProfileId = useSettingsStore((state) => state.activeProfileId); - const resolveHasAPIProfile = async (profileId?: string | null): Promise => { + const resolveHasAPIProfile = useCallback(async (profileId?: string | null): Promise => { // Trust the store when it's already populated to avoid extra IPC calls; fallback to IPC only when empty. if (profileId && profileId !== '') { return true; @@ -37,7 +37,7 @@ export function useIdeationAuth() { } catch { return false; } - }; + }, []); useEffect(() => { const performCheck = async () => { @@ -65,7 +65,7 @@ export function useIdeationAuth() { }, [activeProfileId, resolveHasAPIProfile]); // Expose checkAuth for manual re-checks - const checkAuth = async () => { + const checkAuth = useCallback(async () => { setIsLoading(true); setError(null); @@ -82,7 +82,7 @@ export function useIdeationAuth() { } finally { setIsLoading(false); } - }; + }, [activeProfileId, resolveHasAPIProfile]); return { hasToken, isLoading, error, checkAuth }; }