Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions apps/dokploy/__test__/patches/patch-diff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, expect, it } from "vitest";
import { buildDiffHunks } from "@/lib/patch-diff";

describe("buildDiffHunks", () => {
it("marks inserted lines", () => {
const hunks = buildDiffHunks("a\nb", "a\nb\nc");

expect(hunks).toEqual([
{
type: "equal",
originalStart: 0,
currentStart: 0,
originalLines: ["a", "b"],
currentLines: ["a", "b"],
},
{
type: "insert",
originalStart: 2,
currentStart: 2,
currentLines: ["c"],
},
]);
});

it("marks deleted lines", () => {
const hunks = buildDiffHunks("a\nb\nc", "a\nc");

expect(hunks).toEqual([
{
type: "equal",
originalStart: 0,
currentStart: 0,
originalLines: ["a"],
currentLines: ["a"],
},
{
type: "delete",
originalStart: 1,
currentStart: 1,
originalLines: ["b"],
},
{
type: "equal",
originalStart: 2,
currentStart: 1,
originalLines: ["c"],
currentLines: ["c"],
},
]);
});

it("coalesces replaced blocks", () => {
const hunks = buildDiffHunks("a\nb\nc", "a\nx\ny\nc");

expect(hunks).toEqual([
{
type: "equal",
originalStart: 0,
currentStart: 0,
originalLines: ["a"],
currentLines: ["a"],
},
{
type: "replace",
originalStart: 1,
currentStart: 1,
originalLines: ["b"],
currentLines: ["x", "y"],
},
{
type: "equal",
originalStart: 2,
currentStart: 3,
originalLines: ["c"],
currentLines: ["c"],
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Loader2, Pencil } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -13,7 +12,15 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { PatchDiffEditor, type PatchViewMode } from "./patch-diff-editor";

interface Props {
patchId: string;
Expand All @@ -33,12 +40,26 @@ export const EditPatchDialog = ({
{ enabled: !!patchId },
);
const [content, setContent] = useState("");
const [viewMode, setViewMode] = useState<PatchViewMode>("editor");
const { data: patchFile, isPending: isPatchFileLoading } =
api.patch.readRepoFile.useQuery(
{
id: entityId,
type,
filePath: patch?.filePath || "",
},
{ enabled: !!patch?.filePath },
);

useEffect(() => {
if (patch) {
setContent(patch.content);
if (patchFile) {
setContent(patchFile.patchedContent);
}
}, [patch]);
}, [patchFile]);

useEffect(() => {
setViewMode("editor");
}, [patchId]);

const utils = api.useUtils();
const updatePatch = api.patch.update.useMutation();
Expand All @@ -65,26 +86,42 @@ export const EditPatchDialog = ({
</DialogTrigger>
<DialogContent className="sm:max-w-4xl max-h-[85vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle>Edit Patch</DialogTitle>
<div className="flex items-center justify-between gap-4">
<DialogTitle>Edit Patch</DialogTitle>
<Select
value={viewMode}
onValueChange={(value) => setViewMode(value as PatchViewMode)}
>
<SelectTrigger className="w-[170px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="editor">Editor</SelectItem>
<SelectItem value="diff">Diff mode</SelectItem>
</SelectContent>
</Select>
</div>
<DialogDescription>
{patch ? `Editing: ${patch.filePath}` : "Loading patch..."}
</DialogDescription>
</DialogHeader>
{isPatchLoading ? (
{isPatchLoading || isPatchFileLoading ? (
<div className="flex flex-1 items-center justify-center px-6 py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : (
) : patch && patchFile ? (
<div className="flex-1 min-h-0 px-6 overflow-hidden flex flex-col">
<CodeEditor
<PatchDiffEditor
filePath={patch.filePath}
originalContent={patchFile.originalContent}
value={content}
onChange={(value) => setContent(value ?? "")}
className="h-[400px] w-full"
wrapperClassName="h-[400px]"
lineWrapping
onChange={setContent}
patchType={patch.type}
mode={viewMode}
className="h-[400px]"
/>
</div>
)}
) : null}
<DialogFooter className="px-6 ">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
Expand Down
Loading