Pre-process history for history display so we can write unit tests for it
This commit is contained in:
parent
9f064c57a7
commit
b88320177e
@ -1,4 +1,4 @@
|
||||
import { History, HistoryItemType } from "./history_types";
|
||||
import { History } from "./history_types";
|
||||
import toast from "react-hot-toast";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
@ -7,6 +7,7 @@ import {
|
||||
HoverCardContent,
|
||||
} from "../ui/hover-card";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { renderHistory } from "./utils";
|
||||
|
||||
interface Props {
|
||||
history: History;
|
||||
@ -15,32 +16,19 @@ interface Props {
|
||||
shouldDisableReverts: boolean;
|
||||
}
|
||||
|
||||
function displayHistoryItemType(itemType: HistoryItemType) {
|
||||
switch (itemType) {
|
||||
case "ai_create":
|
||||
return "Create";
|
||||
case "ai_edit":
|
||||
return "Edit";
|
||||
case "code_create":
|
||||
return "Imported from code";
|
||||
default: {
|
||||
const exhaustiveCheck: never = itemType;
|
||||
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function HistoryDisplay({
|
||||
history,
|
||||
currentVersion,
|
||||
revertToVersion,
|
||||
shouldDisableReverts,
|
||||
}: Props) {
|
||||
return history.length === 0 ? null : (
|
||||
const renderedHistory = renderHistory(history, currentVersion);
|
||||
|
||||
return renderedHistory.length === 0 ? null : (
|
||||
<div className="flex flex-col h-screen">
|
||||
<h1 className="font-bold mb-2">Versions</h1>
|
||||
<ul className="space-y-0 flex flex-col-reverse">
|
||||
{history.map((item, index) => (
|
||||
{renderedHistory.map((item, index) => (
|
||||
<li key={index}>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger
|
||||
@ -48,9 +36,8 @@ export default function HistoryDisplay({
|
||||
"flex items-center justify-between space-x-2 p-2",
|
||||
"border-b cursor-pointer",
|
||||
{
|
||||
" hover:bg-black hover:text-white":
|
||||
index !== currentVersion,
|
||||
"bg-slate-500 text-white": index === currentVersion,
|
||||
" hover:bg-black hover:text-white": !item.isActive,
|
||||
"bg-slate-500 text-white": item.isActive,
|
||||
}
|
||||
)}
|
||||
onClick={() =>
|
||||
@ -63,32 +50,16 @@ export default function HistoryDisplay({
|
||||
>
|
||||
{" "}
|
||||
<div className="flex gap-x-1 truncate">
|
||||
<h2 className="text-sm truncate">
|
||||
{item.type === "ai_edit"
|
||||
? item.inputs.prompt
|
||||
: item.type === "ai_create"
|
||||
? "Create"
|
||||
: "Imported from code"}
|
||||
</h2>
|
||||
{/* <h2 className="text-sm">{displayHistoryItemType(item.type)}</h2> */}
|
||||
{item.parentIndex !== null &&
|
||||
item.parentIndex !== index - 1 ? (
|
||||
<h2 className="text-sm">
|
||||
(parent: v{(item.parentIndex || 0) + 1})
|
||||
</h2>
|
||||
) : null}
|
||||
<h2 className="text-sm truncate">{item.summary}</h2>
|
||||
{item.parentVersion !== null && (
|
||||
<h2 className="text-sm">(parent: {item.parentVersion})</h2>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="text-sm">v{index + 1}</h2>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div>
|
||||
{item.type === "ai_edit"
|
||||
? item.inputs.prompt
|
||||
: item.type === "ai_create"
|
||||
? "Create"
|
||||
: "Imported from code"}
|
||||
</div>
|
||||
<Badge>{displayHistoryItemType(item.type)}</Badge>
|
||||
<div>{item.summary}</div>
|
||||
<Badge>{item.type}</Badge>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</li>
|
||||
|
||||
@ -32,3 +32,10 @@ export type CodeCreateInputs = {
|
||||
};
|
||||
|
||||
export type History = HistoryItem[];
|
||||
|
||||
export type RenderedHistoryItem = {
|
||||
type: string;
|
||||
summary: string;
|
||||
parentVersion: string | null;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { extractHistoryTree } from "./utils";
|
||||
import { extractHistoryTree, renderHistory } from "./utils";
|
||||
import type { History } from "./history_types";
|
||||
|
||||
const basicLinearHistory: History = [
|
||||
@ -29,6 +29,18 @@ const basicLinearHistory: History = [
|
||||
},
|
||||
];
|
||||
|
||||
const basicLinearHistoryWithCode: History = [
|
||||
{
|
||||
type: "code_create",
|
||||
parentIndex: null,
|
||||
code: "<html>1. create</html>",
|
||||
inputs: {
|
||||
code: "<html>1. create</html>",
|
||||
},
|
||||
},
|
||||
...basicLinearHistory.slice(1),
|
||||
];
|
||||
|
||||
const basicBranchingHistory: History = [
|
||||
...basicLinearHistory,
|
||||
{
|
||||
@ -121,3 +133,98 @@ test("should correctly extract the history tree", () => {
|
||||
// Bad tree
|
||||
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
||||
});
|
||||
|
||||
test("should correctly render the history tree", () => {
|
||||
expect(renderHistory(basicLinearHistory, 2)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Current version is the first version
|
||||
expect(renderHistory(basicLinearHistory, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a history with code
|
||||
expect(renderHistory(basicLinearHistoryWithCode, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Imported from code",
|
||||
type: "Imported from code",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a non-linear history
|
||||
expect(renderHistory(basicBranchingHistory, 3)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: "v2",
|
||||
summary: "make text green",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { History, HistoryItem } from "./history_types";
|
||||
import {
|
||||
History,
|
||||
HistoryItem,
|
||||
HistoryItemType,
|
||||
RenderedHistoryItem,
|
||||
} from "./history_types";
|
||||
|
||||
export function extractHistoryTree(
|
||||
history: History,
|
||||
@ -30,3 +35,62 @@ export function extractHistoryTree(
|
||||
|
||||
return flatHistory;
|
||||
}
|
||||
|
||||
function displayHistoryItemType(itemType: HistoryItemType) {
|
||||
switch (itemType) {
|
||||
case "ai_create":
|
||||
return "Create";
|
||||
case "ai_edit":
|
||||
return "Edit";
|
||||
case "code_create":
|
||||
return "Imported from code";
|
||||
default: {
|
||||
const exhaustiveCheck: never = itemType;
|
||||
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeHistoryItem(item: HistoryItem) {
|
||||
const itemType = item.type;
|
||||
switch (itemType) {
|
||||
case "ai_create":
|
||||
return "Create";
|
||||
case "ai_edit":
|
||||
return item.inputs.prompt;
|
||||
case "code_create":
|
||||
return "Imported from code";
|
||||
default: {
|
||||
const exhaustiveCheck: never = itemType;
|
||||
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const renderHistory = (
|
||||
history: History,
|
||||
currentVersion: number | null
|
||||
) => {
|
||||
const renderedHistory: RenderedHistoryItem[] = [];
|
||||
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
const item = history[i];
|
||||
// Only show the parent version if it's not the previous version
|
||||
// (i.e. it's the branching point) and if it's not the first version
|
||||
const parentVersion =
|
||||
item.parentIndex !== null && item.parentIndex !== i - 1
|
||||
? `v${(item.parentIndex || 0) + 1}`
|
||||
: null;
|
||||
const type = displayHistoryItemType(item.type);
|
||||
const isActive = i === currentVersion;
|
||||
const summary = summarizeHistoryItem(item);
|
||||
renderedHistory.push({
|
||||
isActive,
|
||||
summary: summary,
|
||||
parentVersion,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
return renderedHistory;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user