这是indexloc提供的服务,不要输入任何密码
Skip to content

test(turbo-utils): improve test coverage #10693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 22, 2025
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
139 changes: 139 additions & 0 deletions packages/turbo-utils/__tests__/managers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os from "node:os";
import { describe, test, expect, beforeEach, jest } from "@jest/globals";
import execa from "execa";
import {
getAvailablePackageManagers,
getPackageManagersBinPaths,
} from "../src/managers";

// Mock dependencies
jest.mock("execa");
jest.mock("node:os");

const mockExeca = execa as jest.MockedFunction<typeof execa>;
const mockOs = os as jest.Mocked<typeof os>;

describe("managers", () => {
beforeEach(() => {
jest.clearAllMocks();
mockOs.tmpdir.mockReturnValue("/tmp");
});

describe("getAvailablePackageManagers", () => {
test("should return all available package managers", async () => {
mockExeca
.mockResolvedValueOnce({ stdout: "1.22.19" } as any) // yarn
.mockResolvedValueOnce({ stdout: "9.5.0" } as any) // npm
.mockResolvedValueOnce({ stdout: "8.6.7" } as any) // pnpm
.mockResolvedValueOnce({ stdout: "1.0.0" } as any); // bun

const result = await getAvailablePackageManagers();

expect(result).toEqual({
yarn: "1.22.19",
npm: "9.5.0",
pnpm: "8.6.7",
bun: "1.0.0",
});
});

test("should return undefined for unavailable package managers", async () => {
mockExeca
.mockResolvedValueOnce({ stdout: "1.22.19" } as any) // yarn
.mockRejectedValueOnce(new Error("npm not found")) // npm
.mockResolvedValueOnce({ stdout: "8.6.7" } as any) // pnpm
.mockRejectedValueOnce(new Error("bun not found")); // bun

const result = await getAvailablePackageManagers();

expect(result).toEqual({
yarn: "1.22.19",
npm: undefined,
pnpm: "8.6.7",
bun: undefined,
});
});

describe("getPackageManagersBinPaths", () => {
test("should return bin paths for all package managers", async () => {
mockExeca
.mockResolvedValueOnce({ stdout: "3.2.1" } as any) // yarn version (berry)
.mockResolvedValueOnce({ stdout: "/usr/local/bin" } as any) // npm prefix
.mockResolvedValueOnce({ stdout: "/usr/local/pnpm" } as any) // pnpm bin
.mockResolvedValueOnce({ stdout: "/usr/local/bun" } as any); // bun bin

const result = await getPackageManagersBinPaths();

expect(result).toEqual({
yarn: ".yarn/releases/yarn-3.2.1.cjs",
npm: "/usr/local/bin",
pnpm: "/usr/local/pnpm",
bun: "/usr/local/bun",
});
});

test("should handle yarn v1 global bin path", async () => {
mockExeca
.mockResolvedValueOnce({ stdout: "1.22.19" } as any) // yarn version check
.mockResolvedValueOnce({ stdout: "/usr/local/bin" } as any) // npm prefix
.mockResolvedValueOnce({ stdout: "/usr/local/pnpm" } as any) // pnpm bin
.mockResolvedValueOnce({ stdout: "/usr/local/bun" } as any) // bun bin
.mockResolvedValueOnce({ stdout: "/usr/local/yarn" } as any); // yarn global bin

const result = await getPackageManagersBinPaths();

expect(result.yarn).toBe("/usr/local/yarn");
expect(result.npm).toBe("/usr/local/bin");
expect(result.pnpm).toBe("/usr/local/pnpm");
expect(result.bun).toBe("/usr/local/bun");
});

test("should return undefined for failed package manager checks", async () => {
mockExeca
.mockRejectedValueOnce(new Error("yarn not found")) // yarn
.mockRejectedValueOnce(new Error("npm not found")) // npm
.mockResolvedValueOnce({ stdout: "/usr/local/pnpm" } as any) // pnpm
.mockRejectedValueOnce(new Error("bun not found")); // bun

const result = await getPackageManagersBinPaths();

expect(result).toEqual({
yarn: undefined,
npm: undefined,
pnpm: "/usr/local/pnpm",
bun: undefined,
});
});

test("should call execa with correct commands for bin paths", async () => {
mockExeca.mockResolvedValue({ stdout: "1.0.0" } as any);

await getPackageManagersBinPaths();

// Verify yarn version check
expect(mockExeca).toHaveBeenCalledWith("yarnpkg", ["--version"], {
cwd: ".",
env: { COREPACK_ENABLE_STRICT: "0" },
});

// Verify other package manager bin path commands
expect(mockExeca).toHaveBeenCalledWith(
"npm",
["config", "get", "prefix"],
{
cwd: "/tmp",
env: { COREPACK_ENABLE_STRICT: "0" },
}
);
expect(mockExeca).toHaveBeenCalledWith("pnpm", ["bin", "--global"], {
cwd: "/tmp",
env: { COREPACK_ENABLE_STRICT: "0" },
});
expect(mockExeca).toHaveBeenCalledWith("bun", ["pm", "--g", "bin"], {
cwd: "/tmp",
env: { COREPACK_ENABLE_STRICT: "0" },
});
});
});
});
});
60 changes: 60 additions & 0 deletions packages/turbo-utils/__tests__/searchUp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import fs from "node:fs";
import path from "node:path";
import { describe, test, expect, beforeEach, jest } from "@jest/globals";
import { searchUp } from "../src/searchUp";

// Mock fs module
jest.mock("node:fs");
const mockFs = fs as jest.Mocked<typeof fs>;

describe("searchUp", () => {
const mockCwd = "/path/to/project/src/components";
const mockRoot = process.platform === "win32" ? "C:\\" : "/";

beforeEach(() => {
jest.clearAllMocks();
// Mock path.parse to return consistent root
jest.spyOn(path, "parse").mockImplementation((pathString: string) => ({
root: mockRoot,
dir: path.dirname(pathString),
base: path.basename(pathString),
ext: path.extname(pathString),
name: path.basename(pathString, path.extname(pathString)),
}));
});

test("should find file in current directory", () => {
const targetFile = "package.json";
mockFs.existsSync.mockImplementation(
(filePath: any) => filePath === path.join(mockCwd, targetFile)
);

const result = searchUp({ target: targetFile, cwd: mockCwd });

expect(result).toBe(mockCwd);
expect(mockFs.existsSync).toHaveBeenCalledWith(
path.join(mockCwd, targetFile)
);
});

test("should find file in parent directory", () => {
const targetFile = "turbo.json";
const parentDir = "/path/to/project";

mockFs.existsSync.mockImplementation(
(filePath: any) => filePath === path.join(parentDir, targetFile)
);

const result = searchUp({ target: targetFile, cwd: mockCwd });

expect(result).toBe(parentDir);
});

test("should return null when file not found", () => {
mockFs.existsSync.mockReturnValue(false);

const result = searchUp({ target: "nonexistent.json", cwd: mockCwd });

expect(result).toBeNull();
});
});
146 changes: 146 additions & 0 deletions packages/turbo-utils/__tests__/validateDirectory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { describe, test, expect, beforeEach, jest } from "@jest/globals";
import path from "node:path";
import fs from "fs-extra";
import { validateDirectory } from "../src/validateDirectory";
import { isFolderEmpty } from "../src/isFolderEmpty";

// Mock dependencies
jest.mock("fs-extra");
jest.mock("../src/isFolderEmpty");

const mockFs = fs as jest.Mocked<typeof fs>;
const mockIsFolderEmpty = isFolderEmpty as jest.MockedFunction<
typeof isFolderEmpty
>;

describe("validateDirectory", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("should return valid result for valid empty directory", () => {
const directory = "/path/to/project";
const resolvedPath = path.resolve(directory);

mockFs.existsSync.mockReturnValue(true);
mockFs.lstatSync.mockReturnValue({
isDirectory: () => true,
} as fs.Stats);
mockIsFolderEmpty.mockReturnValue({
isEmpty: true,
conflicts: [],
});

const result = validateDirectory(directory);

expect(result).toEqual({
valid: true,
root: resolvedPath,
projectName: "project",
});
});

test("should return error when path points to a file", () => {
const directory = "/path/to/file.txt";
const resolvedPath = path.resolve(directory);

mockFs.lstatSync.mockReturnValue({
isDirectory: () => false,
} as fs.Stats);

const result = validateDirectory(directory);

expect(result).toEqual({
valid: false,
root: resolvedPath,
projectName: "file.txt",
error: expect.stringContaining("is not a directory"),
});
});

test("should return error when directory has conflicts", () => {
const directory = "/path/to/existing";
const resolvedPath = path.resolve(directory);
const conflicts = ["package.json", "src/"];

mockFs.existsSync.mockReturnValue(true);
mockFs.lstatSync.mockReturnValue({
isDirectory: () => true,
} as fs.Stats);
mockIsFolderEmpty.mockReturnValue({
isEmpty: false,
conflicts,
});

const result = validateDirectory(directory);

expect(result).toEqual({
valid: false,
root: resolvedPath,
projectName: "existing",
error: expect.stringContaining("has 2 conflicting files"),
});
});

test("should return error with singular 'file' for single conflict", () => {
const directory = "/path/to/existing";
const resolvedPath = path.resolve(directory);
const conflicts = ["package.json"];

mockFs.existsSync.mockReturnValue(true);
mockFs.lstatSync.mockReturnValue({
isDirectory: () => true,
} as fs.Stats);
mockIsFolderEmpty.mockReturnValue({
isEmpty: false,
conflicts,
});

const result = validateDirectory(directory);

expect(result).toEqual({
valid: false,
root: resolvedPath,
projectName: "existing",
error: expect.stringContaining("has 1 conflicting file"),
});
});

test("should handle non-existent directory as valid", () => {
const directory = "/path/to/new-project";
const resolvedPath = path.resolve(directory);

mockFs.existsSync.mockReturnValue(false);
mockFs.lstatSync.mockReturnValue(undefined as any);

const result = validateDirectory(directory);

expect(result).toEqual({
valid: true,
root: resolvedPath,
projectName: "new-project",
});
});

test("should handle lstat errors gracefully", () => {
const directory = "/path/to/project";
const resolvedPath = path.resolve(directory);

mockFs.lstatSync.mockImplementation(() => {
const error = new Error("Permission denied");
(error as any).code = "ENOENT";
throw error;
});

// Since lstatSync is called with throwIfNoEntry: false, it should return null on error
mockFs.lstatSync.mockReturnValue(null as any);

const result = validateDirectory(directory);

expect(result).toEqual({
valid: true,
root: resolvedPath,
projectName: "project",
});
});
});
Loading