Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

167 lines
4.3 KiB
TypeScript

import { act, render, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { MonacoModule } from "./loadMonaco";
import { loadMonaco } from "./loadMonaco";
import { AgentMonacoDiffViewer } from "./AgentMonacoDiffViewer";
vi.mock("./loadMonaco", () => ({
loadMonaco: vi.fn()
}));
describe("AgentMonacoDiffViewer", () => {
beforeEach(() => {
vi.mocked(loadMonaco).mockReset();
});
it("detaches models before disposing the editor and owned models", async () => {
const harness = createMonacoHarness();
vi.mocked(loadMonaco).mockResolvedValue(harness.monaco);
const view = render(
<AgentMonacoDiffViewer
oldValue="before"
newValue="after"
path="/workspace/file.ts"
showHeader={false}
/>
);
await waitFor(() => {
expect(harness.createDiffEditor).toHaveBeenCalledTimes(1);
});
view.unmount();
expect(harness.events).toEqual([
"editor.setModel",
"editor.setModel(null)",
"editor.dispose",
"originalModel.dispose",
"modifiedModel.dispose"
]);
});
it("does not create Monaco resources after unmounting during load", async () => {
const harness = createMonacoHarness();
let resolveMonaco: ((monaco: MonacoModule) => void) | null = null;
vi.mocked(loadMonaco).mockReturnValue(
new Promise((resolve) => {
resolveMonaco = resolve;
})
);
const view = render(
<AgentMonacoDiffViewer
oldValue="before"
newValue="after"
showHeader={false}
/>
);
view.unmount();
await act(async () => {
resolveMonaco?.(harness.monaco);
await Promise.resolve();
});
expect(harness.createModel).not.toHaveBeenCalled();
expect(harness.createDiffEditor).not.toHaveBeenCalled();
});
it("updates the existing models when diff content changes", async () => {
const harness = createMonacoHarness();
vi.mocked(loadMonaco).mockResolvedValue(harness.monaco);
const view = render(
<AgentMonacoDiffViewer
oldValue="before"
newValue="after"
path="/workspace/file.ts"
showHeader={false}
/>
);
await waitFor(() => {
expect(harness.createDiffEditor).toHaveBeenCalledTimes(1);
});
view.rerender(
<AgentMonacoDiffViewer
oldValue="older"
newValue="newer"
path="/workspace/file.ts"
showHeader={false}
/>
);
expect(harness.originalModel.setValue).toHaveBeenCalledWith("older");
expect(harness.modifiedModel.setValue).toHaveBeenCalledWith("newer");
expect(harness.createDiffEditor).toHaveBeenCalledTimes(1);
});
});
function createMonacoHarness(): {
monaco: MonacoModule;
events: string[];
createModel: ReturnType<typeof vi.fn>;
createDiffEditor: ReturnType<typeof vi.fn>;
originalModel: ReturnType<typeof createTextModel>;
modifiedModel: ReturnType<typeof createTextModel>;
} {
const events: string[] = [];
const originalModel = createTextModel("before", "typescript", () => {
events.push("originalModel.dispose");
});
const modifiedModel = createTextModel("after", "typescript", () => {
events.push("modifiedModel.dispose");
});
const createModel = vi
.fn()
.mockReturnValueOnce(originalModel)
.mockReturnValueOnce(modifiedModel);
const editor = {
setModel: vi.fn((model: unknown) => {
events.push(model === null ? "editor.setModel(null)" : "editor.setModel");
}),
dispose: vi.fn(() => {
events.push("editor.dispose");
})
};
const createDiffEditor = vi.fn(() => editor);
const monaco = {
editor: {
createModel,
createDiffEditor,
setTheme: vi.fn(),
setModelLanguage: vi.fn()
}
} as unknown as MonacoModule;
return {
monaco,
events,
createModel,
createDiffEditor,
originalModel,
modifiedModel
};
}
function createTextModel(
initialValue: string,
language: string,
onDispose: () => void
) {
let value = initialValue;
let disposed = false;
return {
getValue: vi.fn(() => value),
setValue: vi.fn((nextValue: string) => {
value = nextValue;
}),
getLanguageId: vi.fn(() => language),
isDisposed: vi.fn(() => disposed),
dispose: vi.fn(() => {
disposed = true;
onDispose();
})
};
}