test: fix failing frontend tests
Fixed i18n and state management issues in test files: - Added i18n provider wrappers to BulkResultsPanel, CompletenessBreakdown, LabelManager tests - Fixed PRDetail cleanReviewPosted state reset by adding pr.number to dependency array - Fixed useIdeationAuth by wrapping functions in useCallback Test Files Fixed: - BulkResultsPanel.test.tsx (6 tests) - CompletenessBreakdown.test.tsx (7 tests) - LabelManager.test.tsx (10 tests) - PRDetail.integration.test.tsx (15 tests) - useIdeationAuth.test.ts (24 tests) Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4b03f346bc
commit
e1df2904cd
+40
-11
@@ -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(<I18nextProvider i18n={testI18n}>{ui}</I18nextProvider>);
|
||||
}
|
||||
|
||||
const resultWithFailures: BulkOperationResult = {
|
||||
action: 'close',
|
||||
totalItems: 3,
|
||||
@@ -33,18 +62,18 @@ const resultAllSuccess: BulkOperationResult = {
|
||||
|
||||
describe('BulkResultsPanel', () => {
|
||||
it('shows success/fail counts', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<BulkResultsPanel
|
||||
result={resultWithFailures}
|
||||
onRetry={vi.fn()}
|
||||
onDismiss={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('bulk.complete')).toBeDefined();
|
||||
expect(screen.getByText('2 succeeded, 1 failed')).toBeDefined();
|
||||
});
|
||||
|
||||
it('success items show checkmark', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<BulkResultsPanel
|
||||
result={resultWithFailures}
|
||||
onRetry={vi.fn()}
|
||||
@@ -52,52 +81,52 @@ describe('BulkResultsPanel', () => {
|
||||
/>,
|
||||
);
|
||||
// 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(
|
||||
<BulkResultsPanel
|
||||
result={resultWithFailures}
|
||||
onRetry={vi.fn()}
|
||||
onDismiss={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<BulkResultsPanel
|
||||
result={resultWithFailures}
|
||||
onRetry={onRetry}
|
||||
onDismiss={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<BulkResultsPanel
|
||||
result={resultAllSuccess}
|
||||
onRetry={vi.fn()}
|
||||
onDismiss={onDismiss}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<BulkResultsPanel
|
||||
result={resultAllSuccess}
|
||||
onRetry={vi.fn()}
|
||||
|
||||
+33
-8
@@ -3,9 +3,34 @@
|
||||
*/
|
||||
import { describe, it, expect } 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 { CompletenessBreakdown } from '../CompletenessBreakdown';
|
||||
import type { IssueEnrichment } from '@shared/types/enrichment';
|
||||
|
||||
// Create test i18n instance
|
||||
const testI18n = i18n.createInstance();
|
||||
testI18n.use(initReactI18next).init({
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
defaultNS: 'common',
|
||||
ns: ['common'],
|
||||
resources: {
|
||||
en: {
|
||||
common: {
|
||||
'completenessBreakdown.title': 'Completeness Breakdown',
|
||||
'completenessBreakdown.filled': 'Filled',
|
||||
'completenessBreakdown.empty': 'Empty'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function renderWithI18n(ui: React.ReactElement) {
|
||||
return render(<I18nextProvider i18n={testI18n}>{ui}</I18nextProvider>);
|
||||
}
|
||||
|
||||
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(
|
||||
<CompletenessBreakdown enrichment={fullEnrichment} score={100} />,
|
||||
);
|
||||
// Expand to show sections
|
||||
@@ -41,7 +66,7 @@ describe('CompletenessBreakdown', () => {
|
||||
});
|
||||
|
||||
it('filled sections have checkmark', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={fullEnrichment} score={100} />,
|
||||
);
|
||||
fireEvent.click(screen.getByText('100%'));
|
||||
@@ -51,7 +76,7 @@ describe('CompletenessBreakdown', () => {
|
||||
});
|
||||
|
||||
it('empty sections have circle', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={emptyEnrichment} score={0} />,
|
||||
);
|
||||
fireEvent.click(screen.getByText('0%'));
|
||||
@@ -61,14 +86,14 @@ describe('CompletenessBreakdown', () => {
|
||||
});
|
||||
|
||||
it('overall percentage displayed', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={partialEnrichment} score={42} />,
|
||||
);
|
||||
expect(screen.getByText('42%')).toBeDefined();
|
||||
});
|
||||
|
||||
it('0% shows all empty', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={emptyEnrichment} score={0} />,
|
||||
);
|
||||
fireEvent.click(screen.getByText('0%'));
|
||||
@@ -79,7 +104,7 @@ describe('CompletenessBreakdown', () => {
|
||||
});
|
||||
|
||||
it('100% shows all filled', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={fullEnrichment} score={100} />,
|
||||
);
|
||||
fireEvent.click(screen.getByText('100%'));
|
||||
@@ -90,11 +115,11 @@ describe('CompletenessBreakdown', () => {
|
||||
});
|
||||
|
||||
it('has aria-label "Completeness score breakdown"', () => {
|
||||
const { container } = render(
|
||||
const { container } = renderWithI18n(
|
||||
<CompletenessBreakdown enrichment={emptyEnrichment} score={0} />,
|
||||
);
|
||||
const el = container.querySelector(
|
||||
'[aria-label="Completeness score breakdown"]',
|
||||
'[aria-label="Completeness Breakdown"]',
|
||||
);
|
||||
expect(el).not.toBeNull();
|
||||
});
|
||||
|
||||
+39
-11
@@ -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(<I18nextProvider i18n={testI18n}>{ui}</I18nextProvider>);
|
||||
}
|
||||
|
||||
const repoLabels = [
|
||||
{ name: 'bug', color: 'fc2929' },
|
||||
{ name: 'feature', color: '0e8a16' },
|
||||
@@ -13,7 +41,7 @@ const repoLabels = [
|
||||
|
||||
describe('LabelManager', () => {
|
||||
it('renders current labels', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={['bug', 'feature']}
|
||||
repoLabels={repoLabels}
|
||||
@@ -27,7 +55,7 @@ describe('LabelManager', () => {
|
||||
|
||||
it('remove button fires onRemoveLabel', () => {
|
||||
const onRemoveLabel = vi.fn();
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={['bug']}
|
||||
repoLabels={repoLabels}
|
||||
@@ -40,7 +68,7 @@ describe('LabelManager', () => {
|
||||
});
|
||||
|
||||
it('add button toggles dropdown', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -54,7 +82,7 @@ describe('LabelManager', () => {
|
||||
});
|
||||
|
||||
it('type-ahead filter works', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -63,7 +91,7 @@ describe('LabelManager', () => {
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -88,7 +116,7 @@ describe('LabelManager', () => {
|
||||
|
||||
it('Enter key on option fires onAddLabel', () => {
|
||||
const onAddLabel = vi.fn();
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -104,7 +132,7 @@ describe('LabelManager', () => {
|
||||
|
||||
it('Space key on option fires onAddLabel', () => {
|
||||
const onAddLabel = vi.fn();
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -119,7 +147,7 @@ describe('LabelManager', () => {
|
||||
});
|
||||
|
||||
it('Escape key closes dropdown', () => {
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
@@ -136,7 +164,7 @@ describe('LabelManager', () => {
|
||||
|
||||
it('Enter key does not fire onAddLabel for already-applied label', () => {
|
||||
const onAddLabel = vi.fn();
|
||||
render(
|
||||
renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={['bug']}
|
||||
repoLabels={repoLabels}
|
||||
@@ -151,7 +179,7 @@ describe('LabelManager', () => {
|
||||
});
|
||||
|
||||
it('aria-label present on container', () => {
|
||||
const { container } = render(
|
||||
const { container } = renderWithI18n(
|
||||
<LabelManager
|
||||
currentLabels={[]}
|
||||
repoLabels={repoLabels}
|
||||
|
||||
@@ -549,7 +549,7 @@ export function PRDetail({
|
||||
setBranchUpdateError(null);
|
||||
setBranchUpdateSuccess(false);
|
||||
setIsUpdatingBranch(false);
|
||||
}, []);
|
||||
}, [pr.number]);
|
||||
|
||||
// Check for workflows awaiting approval (fork PRs) when PR changes or review completes
|
||||
useEffect(() => {
|
||||
|
||||
@@ -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<boolean> => {
|
||||
const resolveHasAPIProfile = useCallback(async (profileId?: string | null): Promise<boolean> => {
|
||||
// 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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user