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 };
}