From 693280d996658e1009f96a306de14263b5054c9a Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 28 Aug 2026 23:05:21 +0200 Subject: [PATCH] fix(app): keep commits above the safe area (#4005) The bottom-anchored commits section did not consume the device inset, so Android system navigation covered its disclosure row. Keep the inset with the section that owns that edge. --- .../commits-section/commits-section.test.tsx | 83 +++++++++++++++++++ .../git/commits-section/commits-section.tsx | 8 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/git/commits-section/commits-section.test.tsx diff --git a/packages/app/src/git/commits-section/commits-section.test.tsx b/packages/app/src/git/commits-section/commits-section.test.tsx new file mode 100644 index 0000000000..cb95c91d33 --- /dev/null +++ b/packages/app/src/git/commits-section/commits-section.test.tsx @@ -0,0 +1,83 @@ +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { JSDOM } from "jsdom"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { CommitsSection } from "./commits-section"; + +vi.mock("react-native-unistyles", () => ({ + StyleSheet: { + create: (factory: unknown) => + typeof factory === "function" + ? factory({ + borderRadius: { full: 9999, sm: 4 }, + borderWidth: { 1: 1 }, + colors: { + border: "#333", + foreground: "#fff", + foregroundMuted: "#aaa", + statusDanger: "#c33", + surface2: "#222", + }, + fontSize: { base: 15, sm: 13 }, + spacing: [0, 4, 8, 12], + }) + : factory, + }, +})); + +vi.mock("react-native-safe-area-context", () => ({ + useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 34, left: 0 }), +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +vi.mock("@/git/use-commits-query", () => ({ + useCheckoutCommitsQuery: () => ({ status: "idle" }), +})); + +vi.mock("@/git/themed-chevron", () => ({ + ThemedChevron: () => null, + chevronColorMapping: () => ({}), +})); + +vi.mock("./commit-row", () => ({ + CommitRow: () => null, +})); + +let root: Root | null = null; + +beforeEach(() => { + const dom = new JSDOM(""); + vi.stubGlobal("React", React); + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + vi.stubGlobal("window", dom.window); + vi.stubGlobal("document", dom.window.document); + vi.stubGlobal("HTMLElement", dom.window.HTMLElement); + vi.stubGlobal("Node", dom.window.Node); + vi.stubGlobal("navigator", dom.window.navigator); + + const container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root?.unmount()); + root = null; + vi.unstubAllGlobals(); +}); + +describe("CommitsSection", () => { + it("keeps its bottom edge above the device safe area", () => { + act(() => { + root?.render( + , + ); + }); + + const header = document.querySelector('[data-testid="commits-section-header"]'); + expect(header?.parentElement?.getAttribute("style")).toContain("padding-bottom: 34px"); + }); +}); diff --git a/packages/app/src/git/commits-section/commits-section.tsx b/packages/app/src/git/commits-section/commits-section.tsx index 29b215410e..c344d44551 100644 --- a/packages/app/src/git/commits-section/commits-section.tsx +++ b/packages/app/src/git/commits-section/commits-section.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Pressable, Text, View } from "react-native"; import { StyleSheet } from "react-native-unistyles"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useTranslation } from "react-i18next"; import { useRetainedPanelActive } from "@/components/retained-panel"; import { useCheckoutCommitsQuery, type CheckoutCommitsQueryResult } from "@/git/use-commits-query"; @@ -91,6 +92,7 @@ export function CommitsSection({ onCollapsedChange, }: CommitsSectionProps) { const { t } = useTranslation(); + const insets = useSafeAreaInsets(); const isPanelActive = useRetainedPanelActive(); const [now, setNow] = useState(() => new Date()); const displayNow = useMemo(() => (isPanelActive ? new Date() : now), [isPanelActive, now]); @@ -119,6 +121,10 @@ export function CommitsSection({ () => [styles.headerChevron, !collapsed && styles.headerChevronExpanded], [collapsed], ); + const containerStyle = useMemo( + () => [styles.container, { paddingBottom: insets.bottom }], + [insets.bottom], + ); if (query.status === "unsupported") { return null; @@ -129,7 +135,7 @@ export function CommitsSection({ : null; return ( - +