diff --git a/apps/mobile/src/features/pull-requests/PullRequestFiles.tsx b/apps/mobile/src/features/pull-requests/PullRequestFiles.tsx index 7244945878861..cfaad50ce90e4 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestFiles.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestFiles.tsx @@ -78,14 +78,20 @@ function describeFiles( { filter, viewed }: ChangedFiles, ) { const noun = (count: number) => (count === 1 ? "file" : "files"); + const hiddenKinds = [ + filter.hideTestFiles ? "tests" : null, + filter.hideDocumentationFiles ? "docs" : null, + ].filter(Boolean); return [ `${parsed.fileCount} ${filter.hiddenCount ? "visible" : "changed"} ${noun(parsed.fileCount)}`, viewed.supported ? `${parsed.files.filter((file) => viewed.isViewed(file.path)).length} viewed` : null, - filter.hideTestFiles - ? `${filter.hiddenCount} test ${noun(filter.hiddenCount)} hidden` - : "Test files included", + filter.hiddenCount > 0 + ? `${filter.hiddenCount} ${hiddenKinds.join(" + ")} ${noun(filter.hiddenCount)} hidden` + : hiddenKinds.length > 0 + ? `${hiddenKinds.join(" + ")} hidden` + : "All file kinds included", ] .filter(Boolean) .join(" · "); @@ -166,31 +172,68 @@ function FileFilterMenu({ filter }: { readonly filter: ChangedFiles["filter"] }) image: "line.3.horizontal.decrease", state: filter.hideTestFiles ? "on" : "off", }, + { + id: "hide-docs", + title: "Hide documentation", + image: "doc.text", + state: filter.hideDocumentationFiles ? "on" : "off", + }, ]} - onPressAction={filter.toggle} + onPressAction={({ nativeEvent }) => { + if (nativeEvent.event === "hide-tests") filter.toggleTests(); + if (nativeEvent.event === "hide-docs") filter.toggleDocumentation(); + }} > ); } +function hiddenFilesEmptyState(filter: ChangedFiles["filter"], nextCursor: string | null) { + const hidingTests = filter.hideTestFiles; + const hidingDocs = filter.hideDocumentationFiles; + if (hidingTests && hidingDocs) { + return { + title: nextCursor ? "No matching files loaded yet" : "Only tests and docs changed", + detail: "Test and documentation files are hidden from this review.", + label: "Show all files", + }; + } + if (hidingDocs) { + return { + title: nextCursor ? "No non-documentation files loaded yet" : "Only documentation changed", + detail: "Documentation files are hidden from this review.", + label: "Show documentation", + }; + } + return { + title: nextCursor ? "No non-test files loaded yet" : "Only test files changed", + detail: "Test files are hidden from this review.", + label: "Show test files", + }; +} + function NoVisibleFiles({ files }: { readonly files: ChangedFiles }) { const { diff, filter, parsed } = files; if (parsed.kind === "files") { - return parsed.files.length === 0 && filter.hiddenCount > 0 ? ( - } - variant="plain" - /> - ) : null; + if (parsed.files.length === 0 && filter.hiddenCount > 0) { + const empty = hiddenFilesEmptyState(filter, diff.nextCursor); + return ( + } + variant="plain" + /> + ); + } + return null; } return !diff.isPending && !diff.error ? ( @@ -425,7 +468,14 @@ export function PullRequestFilesSheet({ route }: StaticScreenProps(EMPTY_IDS); const [visibleFileId, setVisibleFileId] = useState(null); const nativeView = useRef(null); - // The filter the current jump was made under. Hiding or showing test files replaces the rows, - // so a jump made under the other filter has to run again. Native applies the entry + // The filter the current jump was made under. Hiding or showing filtered files replaces the + // rows, so a jump made under another filter has to run again. Native applies the entry // destination before its first viewport, so the first one counts as made. - const jumpedUnder = useRef(targetFile ? filter.hideTestFiles : null); - const hasTestFiles = + const fileFilterKey = `${filter.hideTestFiles}:${filter.hideDocumentationFiles}`; + const jumpedUnder = useRef(targetFile ? fileFilterKey : null); + const hasFilterableFiles = result.parsed.kind === "files" && - result.parsed.files.some((file) => isTestChangedFile(file.path)); + result.parsed.files.some( + (file) => isTestChangedFile(file.path) || isDocumentationChangedFile(file.path), + ); const jumpStarted = () => - jumpedUnder.current !== null && (!hasTestFiles || jumpedUnder.current === filter.hideTestFiles); + jumpedUnder.current !== null && (!hasFilterableFiles || jumpedUnder.current === fileFilterKey); const scrollToFile = (fileId: string) => { if (!nativeView.current) return; - jumpedUnder.current = filter.hideTestFiles; + jumpedUnder.current = fileFilterKey; void nativeView.current.scrollToFile(fileId, false).catch(() => { jumpedUnder.current = null; }); @@ -1147,7 +1150,7 @@ function PullRequestFileRouteContent({ route }: PullRequestFileRouteProps) { nativeView, jumpStarted, setJumpStarted: (started) => { - jumpedUnder.current = started ? filter.hideTestFiles : null; + jumpedUnder.current = started ? fileFilterKey : null; }, onDebug: bridge.onDebug, loadMore, @@ -1160,11 +1163,15 @@ function PullRequestFileRouteContent({ route }: PullRequestFileRouteProps) { ); }; const visibleFile = data.files.find((file) => file.id === visibleFileId) ?? targetFile; - const toggleTestFiles = () => { + const toggleFileFilter = (toggle: () => void) => { setJumpPath(visibleFile?.path ?? route.params.path); setSelectedRowId(null); - filter.toggle(); + toggle(); }; + const hiddenKinds = [ + filter.hideTestFiles ? "tests" : null, + filter.hideDocumentationFiles ? "docs" : null, + ].filter(Boolean); const title = commit ? "Commit changes" : "Changes"; const seekingFile = !targetFile && result.nextCursor !== null; const loadingDiff = (result.isPending && !data.rows.length) || (seekingFile && !result.error); @@ -1172,7 +1179,7 @@ function PullRequestFileRouteContent({ route }: PullRequestFileRouteProps) { navigation.goBack()} menus={[ { @@ -1219,7 +1226,14 @@ function PullRequestFileRouteContent({ route }: PullRequestFileRouteProps) { title: "Hide test files", icon: "line.3.horizontal.decrease", selected: filter.hideTestFiles, - onPress: toggleTestFiles, + onPress: () => toggleFileFilter(filter.toggleTests), + }, + { + id: "hide-docs", + title: "Hide documentation", + icon: "doc.text", + selected: filter.hideDocumentationFiles, + onPress: () => toggleFileFilter(filter.toggleDocumentation), }, ...(selectedRow && selectedFile && canCommentOnDiff ? [ @@ -1366,9 +1380,33 @@ function PullRequestFileRouteContent({ route }: PullRequestFileRouteProps) { !seekingFile ? ( } + title={ + filter.hideTestFiles && filter.hideDocumentationFiles + ? "Only tests and docs changed" + : filter.hideDocumentationFiles + ? "Only documentation changed" + : "Only test files changed" + } + detail={ + filter.hideTestFiles && filter.hideDocumentationFiles + ? "Test and documentation files are hidden from this review." + : filter.hideDocumentationFiles + ? "Documentation files are hidden from this review." + : "Test files are hidden from this review." + } + action={ + + } variant="plain" /> diff --git a/apps/mobile/src/features/pull-requests/pullRequestFileFilters.test.ts b/apps/mobile/src/features/pull-requests/pullRequestFileFilters.test.ts index a7a76abc6e9e4..9725d7b55f37c 100644 --- a/apps/mobile/src/features/pull-requests/pullRequestFileFilters.test.ts +++ b/apps/mobile/src/features/pull-requests/pullRequestFileFilters.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from "vite-plus/test"; import { buildNativeReviewDiffData } from "../review/nativeReviewDiffAdapter"; import { buildReviewParsedDiff } from "../review/reviewModel"; -import { filterPullRequestTestFiles } from "./pullRequestFileFilters"; +import { filterPullRequestFiles } from "./pullRequestFileFilters"; function patch(path: string) { return `diff --git a/${path} b/${path}\n--- a/${path}\n+++ b/${path}\n@@ -1 +1 @@\n-old\n+new\n`; } -describe("mobile PR test file filter", () => { +describe("mobile PR file filter", () => { it("removes whole files from the native diff while retaining source order and line anchors", () => { const parsed = buildReviewParsedDiff( ["src/first.test.ts", "src/first.ts", "src/__tests__/second.ts", "src/second.ts"] @@ -15,7 +15,7 @@ describe("mobile PR test file filter", () => { .join("\n"), "pr", ); - const visible = filterPullRequestTestFiles(parsed, true); + const visible = filterPullRequestFiles(parsed, { hideTestFiles: true }); if (parsed.kind !== "files" || visible.kind !== "files") throw new Error("Expected files"); expect(visible.files.map((file) => file.path)).toEqual(["src/first.ts", "src/second.ts"]); expect(visible.fileCount).toBe(2); @@ -27,7 +27,7 @@ describe("mobile PR test file filter", () => { const visibleData = buildNativeReviewDiffData(visible); const visibleIds = new Set(visible.files.map((file) => file.id)); expect(visibleData.rows).toEqual(allRows.filter((row) => visibleIds.has(row.fileId ?? ""))); - expect(filterPullRequestTestFiles(parsed, false)).toBe(parsed); + expect(filterPullRequestFiles(parsed, { hideTestFiles: false })).toBe(parsed); expect(parsed.files).toHaveLength(4); }); @@ -36,7 +36,10 @@ describe("mobile PR test file filter", () => { ["src/first.test.ts", "src/app.ts", "src/second.test.ts"].map(patch).join("\n"), "pr", ); - const visible = filterPullRequestTestFiles(parsed, true, "src/second.test.ts"); + const visible = filterPullRequestFiles(parsed, { + hideTestFiles: true, + includePath: "src/second.test.ts", + }); if (parsed.kind !== "files" || visible.kind !== "files") throw new Error("Expected files"); expect(visible.files.map((file) => file.path)).toEqual(["src/app.ts", "src/second.test.ts"]); expect(visible.files[1]).toBe(parsed.files[2]); @@ -47,15 +50,15 @@ describe("mobile PR test file filter", () => { "diff --git a/tests/helper.ts b/src/helper.ts\nsimilarity index 100%\nrename from tests/helper.ts\nrename to src/helper.ts\n", "pr", ); - expect(filterPullRequestTestFiles(parsed, true)).toBe(parsed); + expect(filterPullRequestFiles(parsed, { hideTestFiles: true })).toBe(parsed); }); it("keeps an all-test result distinct from a missing diff and preserves truncation notices", () => { const parsed = buildReviewParsedDiff(patch("src/app.test.ts"), "pr"); if (parsed.kind !== "files") throw new Error("Expected files"); - const visible = filterPullRequestTestFiles( + const visible = filterPullRequestFiles( { ...parsed, notice: "More files unavailable" }, - true, + { hideTestFiles: true }, ); expect(visible).toMatchObject({ kind: "files", @@ -66,8 +69,36 @@ describe("mobile PR test file filter", () => { notice: "More files unavailable", }); const raw = { kind: "raw" as const, text: "unparsed", reason: "Unsupported", notice: null }; - expect(filterPullRequestTestFiles(raw, true)).toBe(raw); + expect(filterPullRequestFiles(raw, { hideTestFiles: true })).toBe(raw); const empty = { kind: "empty" as const }; - expect(filterPullRequestTestFiles(empty, true)).toBe(empty); + expect(filterPullRequestFiles(empty, { hideTestFiles: true })).toBe(empty); + }); + + it("hides documentation files independently of tests", () => { + const parsed = buildReviewParsedDiff( + ["README.md", "src/app.ts", "src/app.test.ts", "docs/guide.mdx"].map(patch).join("\n"), + "pr", + ); + const withoutDocs = filterPullRequestFiles(parsed, { hideDocumentationFiles: true }); + if (parsed.kind !== "files" || withoutDocs.kind !== "files") { + throw new Error("Expected files"); + } + expect(withoutDocs.files.map((file) => file.path)).toEqual(["src/app.ts", "src/app.test.ts"]); + const withoutBoth = filterPullRequestFiles(parsed, { + hideTestFiles: true, + hideDocumentationFiles: true, + }); + if (withoutBoth.kind !== "files") throw new Error("Expected files"); + expect(withoutBoth.files.map((file) => file.path)).toEqual(["src/app.ts"]); + const withGuide = filterPullRequestFiles(parsed, { + hideDocumentationFiles: true, + includePath: "docs/guide.mdx", + }); + if (withGuide.kind !== "files") throw new Error("Expected files"); + expect(withGuide.files.map((file) => file.path)).toEqual([ + "src/app.ts", + "src/app.test.ts", + "docs/guide.mdx", + ]); }); }); diff --git a/apps/mobile/src/features/pull-requests/pullRequestFileFilters.ts b/apps/mobile/src/features/pull-requests/pullRequestFileFilters.ts index 2e0424ddbbaed..0d05a077728e5 100644 --- a/apps/mobile/src/features/pull-requests/pullRequestFileFilters.ts +++ b/apps/mobile/src/features/pull-requests/pullRequestFileFilters.ts @@ -1,14 +1,27 @@ -import { isTestChangedFile } from "@t3tools/shared/changedFileKind"; +import { isDocumentationChangedFile, isTestChangedFile } from "@t3tools/shared/changedFileKind"; import type { ReviewParsedDiff } from "../review/reviewModel"; -export function filterPullRequestTestFiles( +export type PullRequestFileFilterOptions = { + readonly hideTestFiles?: boolean; + readonly hideDocumentationFiles?: boolean; + readonly includePath?: string; +}; + +function isHiddenPullRequestFile(path: string, options: PullRequestFileFilterOptions) { + if (options.hideTestFiles && isTestChangedFile(path)) return true; + if (options.hideDocumentationFiles && isDocumentationChangedFile(path)) return true; + return false; +} + +export function filterPullRequestFiles( parsed: ReviewParsedDiff, - hideTestFiles: boolean, - includePath?: string, + options: PullRequestFileFilterOptions, ): ReviewParsedDiff { - if (!hideTestFiles || parsed.kind !== "files") return parsed; + if ((!options.hideTestFiles && !options.hideDocumentationFiles) || parsed.kind !== "files") { + return parsed; + } const files = parsed.files.filter( - (file) => file.path === includePath || !isTestChangedFile(file.path), + (file) => file.path === options.includePath || !isHiddenPullRequestFile(file.path, options), ); if (files.length === parsed.files.length) return parsed; return { diff --git a/apps/mobile/src/features/pull-requests/usePullRequestFileFilter.ts b/apps/mobile/src/features/pull-requests/usePullRequestFileFilter.ts index d8d824c1ae5fc..0fde3997c41ba 100644 --- a/apps/mobile/src/features/pull-requests/usePullRequestFileFilter.ts +++ b/apps/mobile/src/features/pull-requests/usePullRequestFileFilter.ts @@ -3,24 +3,39 @@ import { AsyncResult } from "effect/reactivity"; import { useMemo } from "react"; import type { ReviewParsedDiff } from "../review/reviewModel"; import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences"; -import { filterPullRequestTestFiles } from "./pullRequestFileFilters"; +import { filterPullRequestFiles } from "./pullRequestFileFilters"; export function usePullRequestFileFilter(parsed: ReviewParsedDiff, includePath?: string) { const preferences = useAtomValue(mobilePreferencesAtom); const savePreferences = useAtomSet(updateMobilePreferencesAtom); const hideTestFiles = AsyncResult.isSuccess(preferences) && preferences.value.pullRequestHideTestFiles === true; + const hideDocumentationFiles = + AsyncResult.isSuccess(preferences) && + preferences.value.pullRequestHideDocumentationFiles === true; const visible = useMemo( - () => filterPullRequestTestFiles(parsed, hideTestFiles, includePath), - [parsed, hideTestFiles, includePath], + () => + filterPullRequestFiles(parsed, { + hideTestFiles, + hideDocumentationFiles, + includePath, + }), + [parsed, hideTestFiles, hideDocumentationFiles, includePath], ); const hiddenCount = parsed.kind === "files" && visible.kind === "files" ? parsed.fileCount - visible.fileCount : 0; return { parsed: visible, hideTestFiles, + hideDocumentationFiles, hiddenCount, - toggle: () => savePreferences({ pullRequestHideTestFiles: !hideTestFiles }), - showAll: () => savePreferences({ pullRequestHideTestFiles: false }), + toggleTests: () => savePreferences({ pullRequestHideTestFiles: !hideTestFiles }), + toggleDocumentation: () => + savePreferences({ pullRequestHideDocumentationFiles: !hideDocumentationFiles }), + showAll: () => + savePreferences({ + pullRequestHideTestFiles: false, + pullRequestHideDocumentationFiles: false, + }), }; } diff --git a/apps/mobile/src/persistence/mobile-preferences.ts b/apps/mobile/src/persistence/mobile-preferences.ts index ed2d0b063c767..7e040402e8af3 100644 --- a/apps/mobile/src/persistence/mobile-preferences.ts +++ b/apps/mobile/src/persistence/mobile-preferences.ts @@ -92,6 +92,7 @@ export interface Preferences { readonly navigationTabOrder?: readonly string[]; readonly hiddenNavigationTabs?: readonly string[]; readonly pullRequestHideTestFiles?: boolean; + readonly pullRequestHideDocumentationFiles?: boolean; readonly pullRequestMergeMethod?: PullRequestMergeMethod; readonly liveActivitiesEnabled?: boolean; readonly themeId?: MobileThemeId; @@ -178,6 +179,7 @@ function sanitizePreferences(parsed: Preferences): Preferences { navigationTabOrder?: readonly string[]; hiddenNavigationTabs?: readonly string[]; pullRequestHideTestFiles?: boolean; + pullRequestHideDocumentationFiles?: boolean; pullRequestMergeMethod?: PullRequestMergeMethod; liveActivitiesEnabled?: boolean; themeId?: MobileThemeId; @@ -229,6 +231,10 @@ function sanitizePreferences(parsed: Preferences): Preferences { preferences.pullRequestHideTestFiles = parsed.pullRequestHideTestFiles; } + if (typeof parsed.pullRequestHideDocumentationFiles === "boolean") { + preferences.pullRequestHideDocumentationFiles = parsed.pullRequestHideDocumentationFiles; + } + if ( parsed.pullRequestMergeMethod === "squash" || parsed.pullRequestMergeMethod === "merge" || diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 9767972d73d05..d8feec846a36a 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -12,6 +12,7 @@ import { CheckIcon, ChevronDownIcon, Columns2Icon, + BookTextIcon, FlaskConicalOffIcon, FolderTreeIcon, PilcrowIcon, @@ -41,7 +42,8 @@ import { useDiffPanelStore, } from "../diffPanelStore"; import { useLocalStorage } from "../hooks/useLocalStorage"; -import { isTestChangedFile } from "@t3tools/shared/changedFileKind"; +import { isDocumentationChangedFile, isTestChangedFile } from "@t3tools/shared/changedFileKind"; +import { useHideDocumentationFiles } from "./diffs/useHideDocumentationFiles"; import { useHideTestFiles } from "./diffs/useHideTestFiles"; import { useRenderedAppearance } from "../hooks/useTheme"; import { useStableCallback } from "../hooks/useStableCallback"; @@ -240,6 +242,7 @@ export default function DiffPanel({ Schema.Boolean, ); const [hideTestFiles, setHideTestFiles] = useHideTestFiles(); + const [hideDocumentationFiles, setHideDocumentationFiles] = useHideDocumentationFiles(); const [baseRefQuery, setBaseRefQuery] = useState(""); // The open commit menu keeps the list it opened with, so rows do not move under the pointer. const [openCommitMenu, setOpenCommitMenu] = useState(null); @@ -652,10 +655,16 @@ export default function DiffPanel({ const testFileCount = renderableFiles.filter((file) => isTestChangedFile(resolveFileDiffPath(file)), ).length; + const documentationFileCount = renderableFiles.filter((file) => + isDocumentationChangedFile(resolveFileDiffPath(file)), + ).length; // Hiding is display-only: lazy loading indexes into the full list. A requested file stays visible. const isFileShown = (file: FileDiffMetadata) => { const path = resolveFileDiffPath(file); - return !hideTestFiles || path === selectedFilePath || !isTestChangedFile(path); + if (path === selectedFilePath) return true; + if (hideTestFiles && isTestChangedFile(path)) return false; + if (hideDocumentationFiles && isDocumentationChangedFile(path)) return false; + return true; }; const shownFileEntries = renderableFileEntries.filter(({ fileDiff }) => isFileShown(fileDiff)); const shownFileKeys = shownFileEntries.map((file) => file.fileKey); @@ -1256,6 +1265,28 @@ export default function DiffPanel({ )} + {documentationFileCount > 0 && ( + + setHideDocumentationFiles(Boolean(pressed))} + /> + } + > + + + + {hideDocumentationFiles + ? `Show ${documentationFileCount} hidden documentation ${documentationFileCount === 1 ? "file" : "files"}` + : "Hide documentation"} + + + )} {diffFileKeys.length > 0 && ( isTestChangedFile(resolveFileDiffPath(file)), ).length; - // A requested file stays visible so a jump to a hidden test still lands. - const files = hideTestFiles + const documentationFileCount = loadedFiles.filter((file) => + isDocumentationChangedFile(resolveFileDiffPath(file)), + ).length; + // A requested file stays visible so a jump to a hidden test or doc still lands. + const files = + hideTestFiles || hideDocumentationFiles + ? loadedFiles.filter((file) => { + const path = resolveFileDiffPath(file); + if (path === revealRequest?.path || path === linkPath) return true; + if (hideTestFiles && isTestChangedFile(path)) return false; + if (hideDocumentationFiles && isDocumentationChangedFile(path)) return false; + return true; + }) + : loadedFiles; + const hiddenTestFileCount = hideTestFiles + ? loadedFiles.filter((file) => { + const path = resolveFileDiffPath(file); + return path !== revealRequest?.path && path !== linkPath && isTestChangedFile(path); + }).length + : 0; + const hiddenDocumentationFileCount = hideDocumentationFiles ? loadedFiles.filter((file) => { const path = resolveFileDiffPath(file); - return path === revealRequest?.path || path === linkPath || !isTestChangedFile(path); - }) - : loadedFiles; - const hiddenTestFileCount = loadedFiles.length - files.length; + return ( + path !== revealRequest?.path && path !== linkPath && isDocumentationChangedFile(path) + ); + }).length + : 0; const filePaths = files.map((file) => resolveFileDiffPath(file)); const omittedFileStats = new Map( loadedSlices.flatMap((slice) => @@ -1190,6 +1213,17 @@ function PullRequestCodeTab({ {hiddenTestFileCount} test {hiddenTestFileCount === 1 ? "file" : "files"} hidden ) : null} + {hiddenDocumentationFileCount > 0 ? ( + setHideDocumentationFiles(false)} + > + {hiddenDocumentationFileCount} documentation{" "} + {hiddenDocumentationFileCount === 1 ? "file" : "files"} hidden + + ) : null} {filesViewed.enabled && files.length > 0 ? ( @@ -1423,6 +1457,26 @@ function PullRequestCodeTab({ ) : null} + {documentationFileCount > 0 ? ( + + setHideDocumentationFiles(Boolean(pressed))} + /> + } + > + + + + {hideDocumentationFiles ? "Show documentation" : "Hide documentation"} + + + ) : null} {fileKeys.length > 0 ? ( {commit === null ? `This ${term} has no file changes.` : "This commit has no file changes."} @@ -1639,23 +1694,29 @@ function PullRequestCodeTab({ } }} > - {/* The viewer must own its scroll element to position virtual rows within it. */} - - - className="@container h-full overflow-auto [scrollbar-gutter:stable]" - containerRef={scrollerRef} - viewerRef={codeViewReveal.attach} - items={items} - selectedLines={selectedLines} - onSelectedLinesChange={setSelectedLines} - options={diffViewOptions} - renderCodeViewFooter={renderCodeViewFooter} - renderHeaderPrefix={renderHeaderPrefix} - renderHeaderMetadata={renderHeaderMetadata} - renderAnnotation={renderAnnotation} - unsafeCSSExtra={REPLACE_FILE_COUNTS_CSS} - /> - + {items.length === 0 && loadedFiles.length > 0 ? ( + + All changed files are hidden by the file filters. + + ) : ( + // The viewer must own its scroll element to position virtual rows within it. + + + className="@container h-full overflow-auto [scrollbar-gutter:stable]" + containerRef={scrollerRef} + viewerRef={codeViewReveal.attach} + items={items} + selectedLines={selectedLines} + onSelectedLinesChange={setSelectedLines} + options={diffViewOptions} + renderCodeViewFooter={renderCodeViewFooter} + renderHeaderPrefix={renderHeaderPrefix} + renderHeaderMetadata={renderHeaderMetadata} + renderAnnotation={renderAnnotation} + unsafeCSSExtra={REPLACE_FILE_COUNTS_CSS} + /> + + )} {fileTreeOpen ? ( { it.each([ @@ -16,3 +20,18 @@ describe("classifyChangedFile", () => { expect(classifyChangedFile(path)).toBe(kind); }); }); + +describe("changed file kind helpers", () => { + it.each([ + ["src/app.test.ts", true, false], + ["src/__mocks__/client.ts", true, false], + ["README.md", false, true], + ["docs/guide.mdx", false, true], + ["tests/README.md", true, false], + ["src/app.ts", false, false], + ["requirements.txt", false, false], + ] as const)("%s", (path, test, docs) => { + expect(isTestChangedFile(path)).toBe(test); + expect(isDocumentationChangedFile(path)).toBe(docs); + }); +}); diff --git a/packages/shared/src/changedFileKind.ts b/packages/shared/src/changedFileKind.ts index 8e7d59f15eddf..7f93fad9e630c 100644 --- a/packages/shared/src/changedFileKind.ts +++ b/packages/shared/src/changedFileKind.ts @@ -73,6 +73,10 @@ export function isTestChangedFile(path: string): boolean { return kind === "tests" || kind === "mocks"; } +export function isDocumentationChangedFile(path: string): boolean { + return classifyChangedFile(path) === "docs"; +} + export function classifyChangedFile(path: string): ChangedFileKind { const segments = path.replaceAll("\\", "/").split("/"); const name = segments.pop() ?? "";
+ All changed files are hidden by the file filters. +