Skip to content
Merged
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
3 changes: 2 additions & 1 deletion src/utils/tempFile.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomBytes } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
Expand All @@ -17,7 +18,7 @@ function getFormattedDateTime(): string {
export async function createTempFile(): Promise<string> {
const tempDir = join(tmpdir(), "editprompt-prompts");
await mkdir(tempDir, { recursive: true });
const fileName = `${TEMP_FILE_PREFIX}${getFormattedDateTime()}${TEMP_FILE_EXTENSION}`;
const fileName = `${TEMP_FILE_PREFIX}${getFormattedDateTime()}-${randomBytes(4).toString("hex")}${TEMP_FILE_EXTENSION}`;
const filePath = join(tempDir, fileName);
await writeFile(filePath, "", "utf-8");
return filePath;
Expand Down
108 changes: 67 additions & 41 deletions test/modules/editor.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import {
getEditor,
launchEditor,
openEditorAndGetContent,
readFileContent,
} from "../../src/modules/editor";

async function cleanupTempFile(filePath: string): Promise<void> {
await rm(filePath, { force: true });
await rm(dirname(filePath), { recursive: true, force: true });
}

describe("Editor Module", () => {
beforeEach(() => {
// Reset all mocks
Expand Down Expand Up @@ -156,68 +163,87 @@ describe("Editor Module", () => {

describe("openEditorAndGetContent", () => {
test("should complete full editor workflow successfully", async () => {
const createTempFileMock = mock(() => Promise.resolve("/tmp/test.md"));
void mock.module("../../src/utils/tempFile", () => ({
createTempFile: createTempFileMock,
}));

const mockProcess = {
on: mock((event: string, callback) => {
if (event === "exit") {
setTimeout(() => callback(0), 10);
}
}),
};

const spawnMock = mock(() => mockProcess);
let tempFilePath: string | undefined;

const spawnMock = mock((_editor: string, args: string[]) => {
tempFilePath = args[0];
return {
on: mock((event: string, callback) => {
if (event === "exit") {
setTimeout(() => callback(0), 10);
}
}),
};
});
void mock.module("node:child_process", () => ({
spawn: spawnMock,
}));

const readFileMock = mock(() => Promise.resolve("Test content"));
void mock.module("node:fs/promises", () => ({
mkdir,
readFile: readFileMock,
rm,
writeFile,
}));

const result = await openEditorAndGetContent("vim");
expect(result).toBe("Test content");
expect(createTempFileMock).toHaveBeenCalled();
expect(spawnMock).toHaveBeenCalledWith("vim", ["/tmp/test.md"], {
stdio: "inherit",
shell: true,
env: expect.objectContaining({
EDITPROMPT: "1",
}),
});
expect(readFileMock).toHaveBeenCalledWith("/tmp/test.md", "utf-8");
try {
const result = await openEditorAndGetContent("vim");
expect(result).toBe("Test content");
expect(spawnMock).toHaveBeenCalledWith("vim", [expect.any(String)], {
stdio: "inherit",
shell: true,
env: expect.objectContaining({
EDITPROMPT: "1",
}),
});
} finally {
if (tempFilePath) {
await cleanupTempFile(tempFilePath);
}
}
});

test("should throw error when no content is entered", async () => {
const createTempFileMock = mock(() => Promise.resolve("/tmp/test.md"));
void mock.module("../../src/utils/tempFile", () => ({
createTempFile: createTempFileMock,
}));

const mockProcess = {
on: mock((event: string, callback) => {
if (event === "exit") {
setTimeout(() => callback(0), 10);
}
}),
};

const spawnMock = mock(() => mockProcess);
let tempFilePath: string | undefined;

const spawnMock = mock((_editor: string, args: string[]) => {
tempFilePath = args[0];
return {
on: mock((event: string, callback) => {
if (event === "exit") {
setTimeout(() => callback(0), 10);
}
}),
};
});
void mock.module("node:child_process", () => ({
spawn: spawnMock,
}));

const readFileMock = mock(() => Promise.resolve(""));
void mock.module("node:fs/promises", () => ({
mkdir,
readFile: readFileMock,
rm,
writeFile,
}));

const result = await openEditorAndGetContent("vim");
expect(result).toBe("");
try {
const result = await openEditorAndGetContent("vim");
expect(result).toBe("");
expect(spawnMock).toHaveBeenCalledWith("vim", [expect.any(String)], {
stdio: "inherit",
shell: true,
env: expect.objectContaining({
EDITPROMPT: "1",
}),
});
} finally {
if (tempFilePath) {
await cleanupTempFile(tempFilePath);
}
}
});
});
});
20 changes: 20 additions & 0 deletions test/utils/tempFile.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { readFile, rm } from "node:fs/promises";
import { basename, dirname } from "node:path";

describe("TempFile Utility", () => {
beforeEach(() => {
Expand All @@ -13,6 +15,24 @@ describe("TempFile Utility", () => {
const result = await createTempFile();
expect(result).toBeDefined();
expect(typeof result).toBe("string");
expect(basename(result)).toMatch(/^\.editprompt-\d{14}-[0-9a-f]{8}\.md$/);
expect(await readFile(result, "utf-8")).toBe("");

await rm(result, { force: true });
await rm(dirname(result), { recursive: true, force: true });
});

test("should create a unique path for each call", async () => {
const { createTempFile } = await import("../../src/utils/tempFile");

const firstPath = await createTempFile();
const secondPath = await createTempFile();

expect(firstPath).not.toBe(secondPath);

await rm(firstPath, { force: true });
await rm(secondPath, { force: true });
await rm(dirname(firstPath), { recursive: true, force: true });
});
});
});
Loading