From eab6731a068c207c74d5425518178cf36dee30ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Sat, 19 Sep 2026 03:55:32 +0000 Subject: [PATCH] fix(mobile): make Connect GitHub action full-width on manual review screen Explorer finding: manual-review-connect-provider: The "Connect GitHub" primary action is a small centered pill here but full-width on the near-identical PR Review connect screen, so the same action is not styled the same. The user-agent explorer found this while using the app like a user. One finding per item; the explorer never edits product code. Flow: manual-review-connect-provider Found on revision: fd04ca1ef Repro: 1. set this state first: a wiped install on a brand-new account 2. open the app on emulator-5602 3. reach the screen the capture names (manual-review-connect-provider) 4. the capture shows the defect named below Observed: The "Connect GitHub" primary action is a small centered pill here but full-width on the near-identical PR Review connect screen, so the same action is not styled the same. Expected: the screen renders without this defect Evidence (from the device run): - ~/.local/share/kwf/findings/explorer-manual-review-connect-provider-the-connect-githu-2f462e15/manual-review-connect-provider.png --- .../manual-review-screen.mounted.test.tsx | 106 ++++++++++++++++++ .../code-reviewer/manual-review-screen.tsx | 5 + 2 files changed, 111 insertions(+) create mode 100644 apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx diff --git a/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx b/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx new file mode 100644 index 0000000000..8682f46244 --- /dev/null +++ b/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx @@ -0,0 +1,106 @@ +import { createElement, type ReactElement } from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { act, TestRenderer } from '@/test/renderer'; +import { ManualReviewScreen } from './manual-review-screen'; + +const state = vi.hoisted(() => ({ + github: { + isLoading: false, + isError: false, + isRefetching: false, + data: { connected: false }, + error: null, + refetch: vi.fn(), + }, + gitlab: { + isLoading: false, + isError: false, + isRefetching: false, + data: { connected: false }, + error: null, + refetch: vi.fn(), + }, + push: vi.fn(), +})); + +vi.mock('expo-haptics', () => ({ + selectionAsync: vi.fn(), + notificationAsync: vi.fn(), + NotificationFeedbackType: { Success: 'success' }, +})); +vi.mock('expo-router', () => ({ useRouter: () => ({ push: state.push }) })); +vi.mock('react-native', () => ({ + Pressable: 'Pressable', + TextInput: 'TextInput', + View: 'View', +})); +vi.mock('@/components/agents/model-selector', () => ({ ModelSelector: 'ModelSelector' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field-a11y', () => ({ formFieldA11y: () => 'a11y' })); +vi.mock('@/components/ui/icons', () => ({ Check: 'Check', GitPullRequest: 'GitPullRequest' })); +vi.mock('@/components/ui/radio-group', () => ({ + RadioGroup: 'RadioGroup', + radioItemA11y: () => ({}), +})); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/tab-screen', () => ({ TabScreenScrollView: 'ScrollView' })); +vi.mock('@/lib/code-reviewer-config', () => ({ + PLATFORM_CAPABILITIES: { github: { label: 'GitHub' }, gitlab: { label: 'GitLab' } }, +})); +vi.mock('@/lib/code-reviewer-status', () => ({ + classifyProviderErrorCode: () => ({ permanent: false, variant: 'server' }), +})); +vi.mock('@/lib/hooks/use-available-models', () => ({ useAvailableModels: () => ({ models: [] }) })); +vi.mock('@/lib/hooks/use-code-reviewer', () => ({ + PERSONAL_SCOPE: 'personal', + useGitHubStatus: () => state.github, + useGitLabStatus: () => state.gitlab, + useReviewConfig: () => ({ data: null }), +})); +vi.mock('@/lib/hooks/use-code-reviews', () => ({ + useCreateManualReview: () => ({ mutate: vi.fn(), isPending: false }), +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ foreground: '#000', mutedForeground: '#666' }), +})); + +function mountScreen(): TestRenderer.ReactTestRenderer { + const ref: { current: TestRenderer.ReactTestRenderer | undefined } = { current: undefined }; + act(() => { + ref.current = TestRenderer.create(createElement(ManualReviewScreen, { scope: 'personal' })); + }); + if (!ref.current) { + throw new Error('screen did not render'); + } + return ref.current; +} + +beforeEach(() => { + state.github.data = { connected: false }; + state.gitlab.data = { connected: false }; + state.github.isError = false; + state.gitlab.isError = false; + state.push.mockClear(); +}); + +describe('ManualReviewScreen connect provider CTA', () => { + it('renders the Connect GitHub action full-width like the PR-review connect gate', () => { + const renderer = mountScreen(); + + const empty = renderer.root.findByType('EmptyState'); + const action = empty.props.action as ReactElement<{ className?: string }>; + const className = String(action.props.className); + expect(className).toContain('w-full'); + expect(className).toContain('mt-3'); + + act(() => { + renderer.unmount(); + }); + }); +}); diff --git a/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx b/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx index 570ae045ef..fc78d64d98 100644 --- a/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx +++ b/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx @@ -156,7 +156,12 @@ export function ManualReviewScreen({ scope }: Readonly<{ scope: string }>) { title={t('codeReviewer.manualReview.connectProvider')} description={t('codeReviewer.manualReview.connectProviderDescription')} action={ + // `mt-3 w-full` matches the near-identical PR-review connect gate + // (pr-review-connect-gate.tsx) and the Code Reviewer + // ProviderConnectCard, so the same Connect GitHub action is styled + // the same wherever it appears.