mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Fixes #5207: Add side-by-side diff preview capability for multi-file apply_diff
- Created SideBySideDiffViewer component with unified and split-view modes - Added view mode toggle to BatchDiffApproval component - Integrated VSCode diff editor color variables for proper theming - Added comprehensive test coverage for the new component - Fixed vitest configuration to support React development mode for testing The implementation provides: - File navigation controls for multi-file operations - Parallel columns showing original vs modified content - Color coding for additions, deletions, and unchanged lines - Summary view showing all affected files - Toggle between traditional search/replace and modern side-by-side views
This commit is contained in:
parent
3a8ba27615
commit
944cb4eff2
5 changed files with 553 additions and 17 deletions
|
|
@ -1,5 +1,8 @@
|
|||
import React, { memo, useState } from "react"
|
||||
import { Eye, Code } from "lucide-react"
|
||||
import CodeAccordian from "../common/CodeAccordian"
|
||||
import { SideBySideDiffViewer } from "./SideBySideDiffViewer"
|
||||
import { StandardTooltip } from "@/components/ui"
|
||||
|
||||
interface FileDiff {
|
||||
path: string
|
||||
|
|
@ -19,6 +22,7 @@ interface BatchDiffApprovalProps {
|
|||
|
||||
export const BatchDiffApproval = memo(({ files = [], ts }: BatchDiffApprovalProps) => {
|
||||
const [expandedFiles, setExpandedFiles] = useState<Record<string, boolean>>({})
|
||||
const [viewMode, setViewMode] = useState<"traditional" | "side-by-side">("side-by-side")
|
||||
|
||||
if (!files?.length) {
|
||||
return null
|
||||
|
|
@ -31,26 +35,62 @@ export const BatchDiffApproval = memo(({ files = [], ts }: BatchDiffApprovalProp
|
|||
}))
|
||||
}
|
||||
|
||||
const renderTraditionalView = () => (
|
||||
<div className="flex flex-col gap-0 border border-border rounded-md p-1">
|
||||
{files.map((file) => {
|
||||
// Combine all diffs into a single diff string for this file
|
||||
const combinedDiff = file.diffs?.map((diff) => diff.content).join("\n\n") || file.content
|
||||
|
||||
return (
|
||||
<div key={`${file.path}-${ts}`}>
|
||||
<CodeAccordian
|
||||
path={file.path}
|
||||
code={combinedDiff}
|
||||
language="diff"
|
||||
isExpanded={expandedFiles[file.path] || false}
|
||||
onToggleExpand={() => handleToggleExpand(file.path)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="pt-[5px]">
|
||||
<div className="flex flex-col gap-0 border border-border rounded-md p-1">
|
||||
{files.map((file) => {
|
||||
// Combine all diffs into a single diff string for this file
|
||||
const combinedDiff = file.diffs?.map((diff) => diff.content).join("\n\n") || file.content
|
||||
|
||||
return (
|
||||
<div key={`${file.path}-${ts}`}>
|
||||
<CodeAccordian
|
||||
path={file.path}
|
||||
code={combinedDiff}
|
||||
language="diff"
|
||||
isExpanded={expandedFiles[file.path] || false}
|
||||
onToggleExpand={() => handleToggleExpand(file.path)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{/* View mode toggle */}
|
||||
<div className="flex items-center justify-between mb-3 p-2 bg-vscode-editorWidget-background border border-vscode-panel-border rounded-md">
|
||||
<span className="text-sm text-vscode-foreground">Diff Preview</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<StandardTooltip content="Traditional search/replace view" side="top">
|
||||
<button
|
||||
className={`px-2 py-1 text-xs rounded flex items-center gap-1 ${
|
||||
viewMode === "traditional"
|
||||
? "bg-vscode-button-background text-vscode-button-foreground"
|
||||
: "bg-transparent text-vscode-descriptionForeground hover:bg-vscode-list-hoverBackground"
|
||||
}`}
|
||||
onClick={() => setViewMode("traditional")}>
|
||||
<Code size={12} />
|
||||
Traditional
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
<StandardTooltip content="Side-by-side diff view" side="top">
|
||||
<button
|
||||
className={`px-2 py-1 text-xs rounded flex items-center gap-1 ${
|
||||
viewMode === "side-by-side"
|
||||
? "bg-vscode-button-background text-vscode-button-foreground"
|
||||
: "bg-transparent text-vscode-descriptionForeground hover:bg-vscode-list-hoverBackground"
|
||||
}`}
|
||||
onClick={() => setViewMode("side-by-side")}>
|
||||
<Eye size={12} />
|
||||
Side-by-Side
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Render based on view mode */}
|
||||
{viewMode === "side-by-side" ? <SideBySideDiffViewer files={files} ts={ts} /> : renderTraditionalView()}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
382
webview-ui/src/components/chat/SideBySideDiffViewer.tsx
Normal file
382
webview-ui/src/components/chat/SideBySideDiffViewer.tsx
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
import React, { memo, useState, useMemo } from "react"
|
||||
import { ChevronDown, ChevronUp, FileText } from "lucide-react"
|
||||
import { StandardTooltip } from "@/components/ui"
|
||||
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
|
||||
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
|
||||
|
||||
interface FileDiff {
|
||||
path: string
|
||||
changeCount: number
|
||||
key: string
|
||||
content: string
|
||||
diffs?: Array<{
|
||||
content: string
|
||||
startLine?: number
|
||||
}>
|
||||
}
|
||||
|
||||
interface SideBySideDiffViewerProps {
|
||||
files: FileDiff[]
|
||||
ts: number
|
||||
}
|
||||
|
||||
interface DiffLine {
|
||||
type: "unchanged" | "added" | "removed" | "context"
|
||||
content: string
|
||||
lineNumber?: number
|
||||
originalLineNumber?: number
|
||||
modifiedLineNumber?: number
|
||||
}
|
||||
|
||||
interface ParsedDiff {
|
||||
originalContent: string
|
||||
modifiedContent: string
|
||||
diffLines: DiffLine[]
|
||||
}
|
||||
|
||||
// Parse search/replace diff format into structured diff data
|
||||
const parseDiffContent = (diffContent: string): ParsedDiff => {
|
||||
const lines = diffContent.split("\n")
|
||||
let originalContent = ""
|
||||
let modifiedContent = ""
|
||||
const diffLines: DiffLine[] = []
|
||||
|
||||
let currentSection: "search" | "replace" | "none" = "none"
|
||||
let searchLines: string[] = []
|
||||
let replaceLines: string[] = []
|
||||
let originalLineNum = 1
|
||||
let modifiedLineNum = 1
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
|
||||
if (line.startsWith("<<<<<<< SEARCH")) {
|
||||
currentSection = "search"
|
||||
continue
|
||||
} else if (line === "=======") {
|
||||
currentSection = "replace"
|
||||
continue
|
||||
} else if (line.startsWith(">>>>>>> REPLACE")) {
|
||||
// Process the accumulated search/replace block
|
||||
const maxLines = Math.max(searchLines.length, replaceLines.length)
|
||||
|
||||
for (let j = 0; j < maxLines; j++) {
|
||||
const searchLine = j < searchLines.length ? searchLines[j] : undefined
|
||||
const replaceLine = j < replaceLines.length ? replaceLines[j] : undefined
|
||||
|
||||
if (searchLine !== undefined && replaceLine !== undefined) {
|
||||
if (searchLine === replaceLine) {
|
||||
// Unchanged line
|
||||
diffLines.push({
|
||||
type: "unchanged",
|
||||
content: searchLine,
|
||||
originalLineNumber: originalLineNum++,
|
||||
modifiedLineNumber: modifiedLineNum++,
|
||||
})
|
||||
} else {
|
||||
// Changed line - show as removed then added
|
||||
diffLines.push({
|
||||
type: "removed",
|
||||
content: searchLine,
|
||||
originalLineNumber: originalLineNum++,
|
||||
modifiedLineNumber: undefined,
|
||||
})
|
||||
diffLines.push({
|
||||
type: "added",
|
||||
content: replaceLine,
|
||||
originalLineNumber: undefined,
|
||||
modifiedLineNumber: modifiedLineNum++,
|
||||
})
|
||||
}
|
||||
} else if (searchLine !== undefined) {
|
||||
// Line only in original (removed)
|
||||
diffLines.push({
|
||||
type: "removed",
|
||||
content: searchLine,
|
||||
originalLineNumber: originalLineNum++,
|
||||
modifiedLineNumber: undefined,
|
||||
})
|
||||
} else if (replaceLine !== undefined) {
|
||||
// Line only in modified (added)
|
||||
diffLines.push({
|
||||
type: "added",
|
||||
content: replaceLine,
|
||||
originalLineNumber: undefined,
|
||||
modifiedLineNumber: modifiedLineNum++,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Reset for next block
|
||||
searchLines = []
|
||||
replaceLines = []
|
||||
currentSection = "none"
|
||||
continue
|
||||
}
|
||||
|
||||
if (currentSection === "search") {
|
||||
searchLines.push(line)
|
||||
originalContent += line + "\n"
|
||||
} else if (currentSection === "replace") {
|
||||
replaceLines.push(line)
|
||||
modifiedContent += line + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
originalContent: originalContent.trim(),
|
||||
modifiedContent: modifiedContent.trim(),
|
||||
diffLines,
|
||||
}
|
||||
}
|
||||
|
||||
const DiffLineComponent = memo(({ line, showLineNumbers = true }: { line: DiffLine; showLineNumbers?: boolean }) => {
|
||||
const getLineStyle = () => {
|
||||
switch (line.type) {
|
||||
case "added":
|
||||
return "bg-vscode-diffEditor-insertedTextBackground text-vscode-diffEditor-insertedTextForeground"
|
||||
case "removed":
|
||||
return "bg-vscode-diffEditor-removedTextBackground text-vscode-diffEditor-removedTextForeground"
|
||||
case "unchanged":
|
||||
return "bg-vscode-editor-background text-vscode-editor-foreground"
|
||||
default:
|
||||
return "bg-vscode-editor-background text-vscode-editor-foreground"
|
||||
}
|
||||
}
|
||||
|
||||
const getLinePrefix = () => {
|
||||
switch (line.type) {
|
||||
case "added":
|
||||
return "+"
|
||||
case "removed":
|
||||
return "-"
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex font-mono text-sm leading-relaxed ${getLineStyle()}`}>
|
||||
{showLineNumbers && (
|
||||
<>
|
||||
<div className="w-12 text-right pr-2 text-vscode-editorLineNumber-foreground opacity-60 select-none">
|
||||
{line.originalLineNumber || ""}
|
||||
</div>
|
||||
<div className="w-12 text-right pr-2 text-vscode-editorLineNumber-foreground opacity-60 select-none">
|
||||
{line.modifiedLineNumber || ""}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="w-4 text-center text-vscode-editorLineNumber-foreground opacity-60 select-none">
|
||||
{getLinePrefix()}
|
||||
</div>
|
||||
<div className="flex-1 pl-2 pr-4 whitespace-pre-wrap break-words">{line.content || " "}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
DiffLineComponent.displayName = "DiffLineComponent"
|
||||
|
||||
const FileDiffViewer = memo(
|
||||
({ file, isExpanded, onToggleExpand }: { file: FileDiff; isExpanded: boolean; onToggleExpand: () => void }) => {
|
||||
const [viewMode, setViewMode] = useState<"unified" | "split">("split")
|
||||
|
||||
const parsedDiff = useMemo(() => {
|
||||
const combinedDiff = file.diffs?.map((diff) => diff.content).join("\n\n") || file.content
|
||||
return parseDiffContent(combinedDiff)
|
||||
}, [file])
|
||||
|
||||
const language = useMemo(() => getLanguageFromPath(file.path), [file.path])
|
||||
|
||||
const renderUnifiedView = () => (
|
||||
<div className="border border-vscode-panel-border rounded">
|
||||
<div className="bg-vscode-editor-background">
|
||||
{/* Header */}
|
||||
<div className="flex bg-vscode-editorGroupHeader-tabsBackground text-vscode-tab-activeForeground text-xs font-mono border-b border-vscode-panel-border">
|
||||
<div className="w-12 text-center py-1 border-r border-vscode-panel-border">Old</div>
|
||||
<div className="w-12 text-center py-1 border-r border-vscode-panel-border">New</div>
|
||||
<div className="w-4 text-center py-1 border-r border-vscode-panel-border"></div>
|
||||
<div className="flex-1 py-1 pl-2">{removeLeadingNonAlphanumeric(file.path)}</div>
|
||||
</div>
|
||||
|
||||
{/* Diff content */}
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{parsedDiff.diffLines.map((line, index) => (
|
||||
<DiffLineComponent key={index} line={line} showLineNumbers={true} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderSplitView = () => (
|
||||
<div className="border border-vscode-panel-border rounded">
|
||||
<div className="bg-vscode-editor-background">
|
||||
{/* Header */}
|
||||
<div className="flex bg-vscode-editorGroupHeader-tabsBackground text-vscode-tab-activeForeground text-xs font-mono border-b border-vscode-panel-border">
|
||||
<div className="flex-1 text-center py-1 border-r border-vscode-panel-border">
|
||||
Original ({removeLeadingNonAlphanumeric(file.path)})
|
||||
</div>
|
||||
<div className="flex-1 text-center py-1">
|
||||
Modified ({removeLeadingNonAlphanumeric(file.path)})
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Split diff content */}
|
||||
<div className="flex max-h-96 overflow-hidden">
|
||||
{/* Original content */}
|
||||
<div className="flex-1 border-r border-vscode-panel-border overflow-y-auto">
|
||||
{parsedDiff.diffLines
|
||||
.filter((line) => line.type !== "added")
|
||||
.map((line, index) => (
|
||||
<div
|
||||
key={`orig-${index}`}
|
||||
className={`flex font-mono text-sm leading-relaxed ${
|
||||
line.type === "removed"
|
||||
? "bg-vscode-diffEditor-removedTextBackground text-vscode-diffEditor-removedTextForeground"
|
||||
: "bg-vscode-editor-background text-vscode-editor-foreground"
|
||||
}`}>
|
||||
<div className="w-12 text-right pr-2 text-vscode-editorLineNumber-foreground opacity-60 select-none">
|
||||
{line.originalLineNumber || ""}
|
||||
</div>
|
||||
<div className="flex-1 pl-2 pr-4 whitespace-pre-wrap break-words">
|
||||
{line.content || " "}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Modified content */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{parsedDiff.diffLines
|
||||
.filter((line) => line.type !== "removed")
|
||||
.map((line, index) => (
|
||||
<div
|
||||
key={`mod-${index}`}
|
||||
className={`flex font-mono text-sm leading-relaxed ${
|
||||
line.type === "added"
|
||||
? "bg-vscode-diffEditor-insertedTextBackground text-vscode-diffEditor-insertedTextForeground"
|
||||
: "bg-vscode-editor-background text-vscode-editor-foreground"
|
||||
}`}>
|
||||
<div className="w-12 text-right pr-2 text-vscode-editorLineNumber-foreground opacity-60 select-none">
|
||||
{line.modifiedLineNumber || ""}
|
||||
</div>
|
||||
<div className="flex-1 pl-2 pr-4 whitespace-pre-wrap break-words">
|
||||
{line.content || " "}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="border border-vscode-panel-border rounded-md">
|
||||
{/* File header */}
|
||||
<div
|
||||
className="flex items-center justify-between p-3 bg-vscode-editorGroupHeader-tabsBackground border-b border-vscode-panel-border cursor-pointer hover:bg-vscode-list-hoverBackground"
|
||||
onClick={onToggleExpand}>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText size={16} className="text-vscode-symbolIcon-fileForeground" />
|
||||
<span className="font-mono text-sm text-vscode-tab-activeForeground">
|
||||
{removeLeadingNonAlphanumeric(file.path)}
|
||||
</span>
|
||||
<span className="text-xs text-vscode-descriptionForeground">
|
||||
({file.changeCount} {file.changeCount === 1 ? "change" : "changes"})
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isExpanded && (
|
||||
<div className="flex items-center gap-1">
|
||||
<StandardTooltip content="Toggle view mode" side="top">
|
||||
<button
|
||||
className="px-2 py-1 text-xs bg-vscode-button-background text-vscode-button-foreground rounded hover:bg-vscode-button-hoverBackground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setViewMode(viewMode === "split" ? "unified" : "split")
|
||||
}}>
|
||||
{viewMode === "split" ? "Unified" : "Split"}
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
)}
|
||||
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diff content */}
|
||||
{isExpanded && (
|
||||
<div className="p-3">{viewMode === "split" ? renderSplitView() : renderUnifiedView()}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
FileDiffViewer.displayName = "FileDiffViewer"
|
||||
|
||||
export const SideBySideDiffViewer = memo(({ files = [], ts }: SideBySideDiffViewerProps) => {
|
||||
const [expandedFiles, setExpandedFiles] = useState<Record<string, boolean>>({})
|
||||
const [allExpanded, setAllExpanded] = useState(false)
|
||||
|
||||
if (!files?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleToggleExpand = (filePath: string) => {
|
||||
setExpandedFiles((prev) => ({
|
||||
...prev,
|
||||
[filePath]: !prev[filePath],
|
||||
}))
|
||||
}
|
||||
|
||||
const handleToggleAll = () => {
|
||||
const newState = !allExpanded
|
||||
setAllExpanded(newState)
|
||||
const newExpandedFiles: Record<string, boolean> = {}
|
||||
files.forEach((file) => {
|
||||
newExpandedFiles[file.path] = newState
|
||||
})
|
||||
setExpandedFiles(newExpandedFiles)
|
||||
}
|
||||
|
||||
const totalChanges = files.reduce((sum, file) => sum + file.changeCount, 0)
|
||||
|
||||
return (
|
||||
<div className="pt-[5px]">
|
||||
{/* Summary header */}
|
||||
<div className="flex items-center justify-between mb-3 p-3 bg-vscode-editorWidget-background border border-vscode-panel-border rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText size={16} className="text-vscode-symbolIcon-fileForeground" />
|
||||
<span className="text-sm font-medium text-vscode-foreground">
|
||||
{files.length} {files.length === 1 ? "file" : "files"} with {totalChanges}{" "}
|
||||
{totalChanges === 1 ? "change" : "changes"}
|
||||
</span>
|
||||
</div>
|
||||
<StandardTooltip content={allExpanded ? "Collapse all files" : "Expand all files"} side="top">
|
||||
<button
|
||||
className="px-3 py-1 text-xs bg-vscode-button-background text-vscode-button-foreground rounded hover:bg-vscode-button-hoverBackground"
|
||||
onClick={handleToggleAll}>
|
||||
{allExpanded ? "Collapse All" : "Expand All"}
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
||||
{/* File diffs */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{files.map((file) => (
|
||||
<FileDiffViewer
|
||||
key={`${file.path}-${ts}`}
|
||||
file={file}
|
||||
isExpanded={expandedFiles[file.path] || allExpanded}
|
||||
onToggleExpand={() => handleToggleExpand(file.path)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
SideBySideDiffViewer.displayName = "SideBySideDiffViewer"
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { describe, it, expect, vi } from "vitest"
|
||||
import { render, screen } from "@testing-library/react"
|
||||
import { SideBySideDiffViewer } from "../SideBySideDiffViewer"
|
||||
|
||||
// Mock the StandardTooltip component
|
||||
vi.mock("@/components/ui", () => ({
|
||||
StandardTooltip: ({ children, content }: { children: React.ReactNode; content: string }) => (
|
||||
<div title={content}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock the utility functions
|
||||
vi.mock("@src/utils/getLanguageFromPath", () => ({
|
||||
getLanguageFromPath: vi.fn(() => "javascript"),
|
||||
}))
|
||||
|
||||
vi.mock("@src/utils/removeLeadingNonAlphanumeric", () => ({
|
||||
removeLeadingNonAlphanumeric: vi.fn((path: string) => path),
|
||||
}))
|
||||
|
||||
describe("SideBySideDiffViewer", () => {
|
||||
const mockFiles = [
|
||||
{
|
||||
path: "test.js",
|
||||
changeCount: 2,
|
||||
key: "test.js (2 changes)",
|
||||
content: "test content",
|
||||
diffs: [
|
||||
{
|
||||
content: `<<<<<<< SEARCH
|
||||
const oldFunction = () => {
|
||||
return "old";
|
||||
}
|
||||
=======
|
||||
const newFunction = () => {
|
||||
return "new";
|
||||
}
|
||||
>>>>>>> REPLACE`,
|
||||
startLine: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
it("renders without crashing", () => {
|
||||
render(<SideBySideDiffViewer files={mockFiles} ts={Date.now()} />)
|
||||
expect(screen.getByText("1 file with 2 changes")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders file summary correctly", () => {
|
||||
render(<SideBySideDiffViewer files={mockFiles} ts={Date.now()} />)
|
||||
expect(screen.getByText("1 file with 2 changes")).toBeInTheDocument()
|
||||
expect(screen.getByText("Expand All")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders multiple files correctly", () => {
|
||||
const multipleFiles = [
|
||||
...mockFiles,
|
||||
{
|
||||
path: "test2.js",
|
||||
changeCount: 1,
|
||||
key: "test2.js (1 change)",
|
||||
content: "test content 2",
|
||||
diffs: [
|
||||
{
|
||||
content: `<<<<<<< SEARCH
|
||||
old line
|
||||
=======
|
||||
new line
|
||||
>>>>>>> REPLACE`,
|
||||
startLine: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
render(<SideBySideDiffViewer files={multipleFiles} ts={Date.now()} />)
|
||||
expect(screen.getByText("2 files with 3 changes")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("returns null when no files provided", () => {
|
||||
const { container } = render(<SideBySideDiffViewer files={[]} ts={Date.now()} />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it("handles undefined files gracefully", () => {
|
||||
const { container } = render(<SideBySideDiffViewer files={undefined as any} ts={Date.now()} />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
@ -132,6 +132,27 @@
|
|||
--color-vscode-textLink-foreground: var(--vscode-textLink-foreground);
|
||||
--color-vscode-textCodeBlock-background: var(--vscode-textCodeBlock-background);
|
||||
--color-vscode-button-hoverBackground: var(--vscode-button-hoverBackground);
|
||||
|
||||
/* Diff editor colors */
|
||||
--color-vscode-diffEditor-insertedTextBackground: var(--vscode-diffEditor-insertedTextBackground);
|
||||
--color-vscode-diffEditor-insertedTextForeground: var(
|
||||
--vscode-diffEditor-insertedTextForeground,
|
||||
var(--vscode-editor-foreground)
|
||||
);
|
||||
--color-vscode-diffEditor-removedTextBackground: var(--vscode-diffEditor-removedTextBackground);
|
||||
--color-vscode-diffEditor-removedTextForeground: var(
|
||||
--vscode-diffEditor-removedTextForeground,
|
||||
var(--vscode-editor-foreground)
|
||||
);
|
||||
|
||||
/* Editor group and tab colors */
|
||||
--color-vscode-editorGroupHeader-tabsBackground: var(--vscode-editorGroupHeader-tabsBackground);
|
||||
--color-vscode-tab-activeForeground: var(--vscode-tab-activeForeground);
|
||||
--color-vscode-editorWidget-background: var(--vscode-editorWidget-background);
|
||||
--color-vscode-editorLineNumber-foreground: var(--vscode-editorLineNumber-foreground);
|
||||
|
||||
/* Symbol icon colors */
|
||||
--color-vscode-symbolIcon-fileForeground: var(--vscode-symbolIcon-fileForeground, var(--vscode-foreground));
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ export default defineConfig({
|
|||
environment: "jsdom",
|
||||
include: ["src/**/*.spec.ts", "src/**/*.spec.tsx"],
|
||||
},
|
||||
define: {
|
||||
"process.env.NODE_ENV": '"development"',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue