diff options
| author | 2023-04-28 01:36:44 +0800 | |
|---|---|---|
| committer | 2023-04-28 01:36:44 +0800 | |
| commit | dd84b9d64fb98746a230cd24233ff50a562c39c9 (patch) | |
| tree | b583261ef00b3afe72ec4d6dacb31e57779a6faf /packages/create-turbo/__tests__ | |
| parent | 0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff) | |
| download | HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip | |
Diffstat (limited to 'packages/create-turbo/__tests__')
| -rw-r--r-- | packages/create-turbo/__tests__/examples.test.ts | 134 | ||||
| -rw-r--r-- | packages/create-turbo/__tests__/git.test.ts | 239 | ||||
| -rw-r--r-- | packages/create-turbo/__tests__/index.test.ts | 90 | ||||
| -rw-r--r-- | packages/create-turbo/__tests__/isFolderEmpty.test.ts | 41 | ||||
| -rw-r--r-- | packages/create-turbo/__tests__/isWritable.test.ts | 35 | ||||
| -rw-r--r-- | packages/create-turbo/__tests__/test-utils.ts | 34 |
6 files changed, 573 insertions, 0 deletions
diff --git a/packages/create-turbo/__tests__/examples.test.ts b/packages/create-turbo/__tests__/examples.test.ts new file mode 100644 index 0000000..20d4464 --- /dev/null +++ b/packages/create-turbo/__tests__/examples.test.ts @@ -0,0 +1,134 @@ +import got from "got"; +import * as Got from "got"; +import { isUrlOk, getRepoInfo, hasRepo } from "../src/utils/examples"; + +jest.mock("got", () => ({ + __esModule: true, + ...jest.requireActual("got"), +})); + +describe("examples", () => { + describe("isUrlOk", () => { + it("returns true if url returns 200", async () => { + const mockGot = jest + .spyOn(got, "head") + .mockReturnValue({ statusCode: 200 } as any); + + const url = "https://github.com/vercel/turbo/"; + const result = await isUrlOk(url); + expect(result).toBe(true); + + expect(mockGot).toHaveBeenCalledWith(url); + mockGot.mockRestore(); + }); + + it("returns false if url returns status != 200", async () => { + const mockGot = jest + .spyOn(got, "head") + .mockReturnValue({ statusCode: 401 } as any); + + const url = "https://not-github.com/vercel/turbo/"; + const result = await isUrlOk(url); + expect(result).toBe(false); + + expect(mockGot).toHaveBeenCalledWith(url); + mockGot.mockRestore(); + }); + }); + + describe("getRepoInfo", () => { + test.each([ + { + repoUrl: "https://github.com/vercel/turbo/", + examplePath: undefined, + defaultBranch: "main", + expectBranchLookup: true, + expected: { + username: "vercel", + name: "turbo", + branch: "main", + filePath: "", + }, + }, + { + repoUrl: + "https://github.com/vercel/turbo/tree/canary/examples/kitchen-sink", + examplePath: undefined, + defaultBranch: "canary", + expectBranchLookup: false, + expected: { + username: "vercel", + name: "turbo", + branch: "canary", + filePath: "examples/kitchen-sink", + }, + }, + { + repoUrl: "https://github.com/vercel/turbo/tree/tek/test-branch/", + examplePath: "examples/basic", + defaultBranch: "canary", + expectBranchLookup: false, + expected: { + username: "vercel", + name: "turbo", + branch: "tek/test-branch", + filePath: "examples/basic", + }, + }, + ])( + "retrieves repo info for $repoUrl and $examplePath", + async ({ + repoUrl, + examplePath, + defaultBranch, + expectBranchLookup, + expected, + }) => { + const mockGot = jest.spyOn(Got, "default").mockReturnValue({ + body: JSON.stringify({ default_branch: defaultBranch }), + } as any); + + const url = new URL(repoUrl); + const result = await getRepoInfo(url, examplePath); + expect(result).toMatchObject(expected); + + if (result && expectBranchLookup) { + expect(mockGot).toHaveBeenCalledWith( + `https://api.github.com/repos/${result.username}/${result.name}` + ); + } + + mockGot.mockRestore(); + } + ); + }); + + describe("hasRepo", () => { + test.each([ + { + repoInfo: { + username: "vercel", + name: "turbo", + branch: "main", + filePath: "", + }, + expected: true, + expectedUrl: + "https://api.github.com/repos/vercel/turbo/contents/package.json?ref=main", + }, + ])( + "checks repo at $expectedUrl", + async ({ expected, repoInfo, expectedUrl }) => { + const mockGot = jest + .spyOn(got, "head") + .mockReturnValue({ statusCode: 200 } as any); + + const result = await hasRepo(repoInfo); + expect(result).toBe(expected); + + expect(mockGot).toHaveBeenCalledWith(expectedUrl); + mockGot.mockRestore(); + } + ); + }); +}); diff --git a/packages/create-turbo/__tests__/git.test.ts b/packages/create-turbo/__tests__/git.test.ts new file mode 100644 index 0000000..27ac118 --- /dev/null +++ b/packages/create-turbo/__tests__/git.test.ts @@ -0,0 +1,239 @@ +import path from "path"; +import { + DEFAULT_IGNORE, + GIT_REPO_COMMAND, + HG_REPO_COMMAND, + isInGitRepository, + isInMercurialRepository, + tryGitInit, +} from "../src/utils/git"; +import childProcess from "child_process"; +import { setupTestFixtures } from "@turbo/test-utils"; + +describe("git", () => { + // just to make sure this doesn't get lost + it("default .gitignore includes .turbo", async () => { + expect(DEFAULT_IGNORE).toContain(".turbo"); + }); + + describe("isInGitRepository", () => { + it("returns true when in a repo", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockReturnValue("true"); + + const result = isInGitRepository(); + expect(result).toBe(true); + + expect(mockExecSync).toHaveBeenCalledWith(GIT_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + + it("returns false when not in a repo", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementation(() => { + throw new Error( + "fatal: not a git repository (or any of the parent directories): .git" + ); + }); + + const result = isInGitRepository(); + expect(result).toBe(false); + + expect(mockExecSync).toHaveBeenCalledWith(GIT_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + + it("returns false on error", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementation(() => { + throw new Error("unknown error"); + }); + + const result = isInGitRepository(); + expect(result).toBe(false); + + expect(mockExecSync).toHaveBeenCalledWith(GIT_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + }); + + describe("isInMercurialRepository", () => { + it("returns true when in a repo", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockReturnValue("true"); + + const result = isInMercurialRepository(); + expect(result).toBe(true); + + expect(mockExecSync).toHaveBeenCalledWith(HG_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + + it("returns false when not in a repo", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementation(() => { + throw new Error("abort: no repository found (.hg not found)"); + }); + + const result = isInMercurialRepository(); + expect(result).toBe(false); + + expect(mockExecSync).toHaveBeenCalledWith(HG_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + + it("returns false on error", async () => { + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementation(() => { + throw new Error("unknown error"); + }); + + const result = isInMercurialRepository(); + expect(result).toBe(false); + + expect(mockExecSync).toHaveBeenCalledWith(HG_REPO_COMMAND, { + stdio: "ignore", + }); + mockExecSync.mockRestore(); + }); + }); + + describe("tryGitInit", () => { + const { useFixture } = setupTestFixtures({ + directory: path.join(__dirname, "../"), + }); + + it("inits a repo succesfully", async () => { + const { root } = useFixture({ fixture: `git` }); + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockReturnValueOnce("git version 2.38.1") + .mockImplementationOnce(() => { + throw new Error( + "fatal: not a git repository (or any of the parent directories): .git" + ); + }) + .mockImplementationOnce(() => { + throw new Error("abort: no repository found (.hg not found)"); + }) + .mockReturnValue("success"); + + const result = tryGitInit(root, "test commit"); + expect(result).toBe(true); + + const calls = [ + "git --version", + "git init", + "git checkout -b main", + "git add -A", + 'git commit -m "test commit"', + ]; + expect(mockExecSync).toHaveBeenCalledTimes(calls.length + 2); + calls.forEach((call) => { + expect(mockExecSync).toHaveBeenCalledWith(call, { + stdio: "ignore", + }); + }); + mockExecSync.mockRestore(); + }); + + it("skips init if already in a repo", async () => { + const { root } = useFixture({ fixture: `git` }); + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockReturnValueOnce("git version 2.38.1") + .mockReturnValueOnce("true") + .mockReturnValue("success"); + + const result = tryGitInit(root, "test commit"); + expect(result).toBe(false); + + const calls = ["git --version"]; + + // 1 call for git --version, 1 call for isInGitRepository + expect(mockExecSync).toHaveBeenCalledTimes(calls.length + 1); + calls.forEach((call) => { + expect(mockExecSync).toHaveBeenCalledWith(call, { + stdio: "ignore", + }); + }); + mockExecSync.mockRestore(); + }); + + it("returns false on unexpected error", async () => { + const { root } = useFixture({ fixture: `git` }); + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementationOnce(() => { + throw new Error("fatal: unknown command git"); + }); + + const result = tryGitInit(root, "test commit"); + expect(result).toBe(false); + + const calls = ["git --version"]; + + expect(mockExecSync).toHaveBeenCalledTimes(calls.length); + calls.forEach((call) => { + expect(mockExecSync).toHaveBeenCalledWith(call, { + stdio: "ignore", + }); + }); + mockExecSync.mockRestore(); + }); + + it("cleans up from partial init on failure", async () => { + const { root } = useFixture({ fixture: `git` }); + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockReturnValueOnce("git version 2.38.1") + .mockImplementationOnce(() => { + throw new Error( + "fatal: not a git repository (or any of the parent directories): .git" + ); + }) + .mockImplementationOnce(() => { + throw new Error("abort: no repository found (.hg not found)"); + }) + .mockReturnValueOnce("success") + .mockReturnValueOnce("success") + .mockImplementationOnce(() => { + throw new Error("fatal: could not add files"); + }); + + const result = tryGitInit(root, "test commit"); + expect(result).toBe(false); + + const calls = [ + "git --version", + "git init", + "git checkout -b main", + "git add -A", + ]; + + expect(mockExecSync).toHaveBeenCalledTimes(calls.length + 2); + calls.forEach((call) => { + expect(mockExecSync).toHaveBeenCalledWith(call, { + stdio: "ignore", + }); + }); + mockExecSync.mockRestore(); + }); + }); +}); diff --git a/packages/create-turbo/__tests__/index.test.ts b/packages/create-turbo/__tests__/index.test.ts new file mode 100644 index 0000000..641b193 --- /dev/null +++ b/packages/create-turbo/__tests__/index.test.ts @@ -0,0 +1,90 @@ +import path from "path"; +import chalk from "chalk"; +import childProcess from "child_process"; +import { setupTestFixtures, spyConsole } from "@turbo/test-utils"; +import { create } from "../src/commands/create"; +import type { CreateCommandArgument } from "../src/commands/create/types"; +import { turboGradient } from "../src/logger"; +import type { PackageManager } from "@turbo/workspaces"; + +// imports for mocks +import * as createProject from "../src/commands/create/createProject"; +import * as turboWorkspaces from "@turbo/workspaces"; +import { getWorkspaceDetailsMockReturnValue } from "./test-utils"; + +jest.mock("@turbo/workspaces", () => ({ + __esModule: true, + ...jest.requireActual("@turbo/workspaces"), +})); + +describe("create-turbo", () => { + const { useFixture } = setupTestFixtures({ + directory: path.join(__dirname, "../"), + }); + + const mockConsole = spyConsole(); + + test.each<{ packageManager: PackageManager }>([ + { packageManager: "yarn" }, + { packageManager: "npm" }, + { packageManager: "pnpm" }, + ])( + "outputs expected console messages when using $packageManager", + async ({ packageManager }) => { + const { root } = useFixture({ fixture: `create-turbo` }); + + const availableScripts = ["build", "test", "dev"]; + + const mockCreateProject = jest + .spyOn(createProject, "createProject") + .mockResolvedValue({ + cdPath: "", + hasPackageJson: true, + availableScripts, + }); + + const mockGetWorkspaceDetails = jest + .spyOn(turboWorkspaces, "getWorkspaceDetails") + .mockResolvedValue( + getWorkspaceDetailsMockReturnValue({ + root, + packageManager, + }) + ); + + const mockExecSync = jest + .spyOn(childProcess, "execSync") + .mockImplementation(() => { + return "success"; + }); + + await create( + root as CreateCommandArgument, + packageManager as CreateCommandArgument, + { + skipInstall: true, + example: "default", + } + ); + + const expected = `${chalk.bold( + turboGradient(">>> Success!") + )} Created a new Turborepo at "${path.relative(process.cwd(), root)}".`; + + expect(mockConsole.log).toHaveBeenCalledWith(expected); + expect(mockConsole.log).toHaveBeenCalledWith( + "Inside that directory, you can run several commands:" + ); + + availableScripts.forEach((script) => { + expect(mockConsole.log).toHaveBeenCalledWith( + chalk.cyan(` ${packageManager} run ${script}`) + ); + }); + + mockCreateProject.mockRestore(); + mockGetWorkspaceDetails.mockRestore(); + mockExecSync.mockRestore(); + } + ); +}); diff --git a/packages/create-turbo/__tests__/isFolderEmpty.test.ts b/packages/create-turbo/__tests__/isFolderEmpty.test.ts new file mode 100644 index 0000000..66b2310 --- /dev/null +++ b/packages/create-turbo/__tests__/isFolderEmpty.test.ts @@ -0,0 +1,41 @@ +import fs from "fs-extra"; +import path from "path"; +import { isFolderEmpty } from "../src/utils/isFolderEmpty"; +import { setupTestFixtures } from "@turbo/test-utils"; + +describe("isFolderEmpty", () => { + const { useFixture } = setupTestFixtures({ + directory: path.join(__dirname, "../"), + }); + + it("correctly identifies an empty directory", async () => { + const { root } = useFixture({ fixture: `is-folder-empty` }); + const result = isFolderEmpty(root); + expect(result.isEmpty).toEqual(true); + expect(result.conflicts).toEqual([]); + }); + + it("correctly identifies a directory with non-conflicting files", async () => { + const { root } = useFixture({ fixture: `is-folder-empty` }); + fs.writeFileSync(path.join(root, "LICENSE"), "MIT"); + const result = isFolderEmpty(root); + expect(result.isEmpty).toEqual(true); + expect(result.conflicts).toEqual([]); + }); + + it("correctly identifies a directory non-conflicting files (intelliJ)", async () => { + const { root } = useFixture({ fixture: `is-folder-empty` }); + fs.writeFileSync(path.join(root, "intellij-idea-config.iml"), "{}"); + const result = isFolderEmpty(root); + expect(result.isEmpty).toEqual(true); + expect(result.conflicts).toEqual([]); + }); + + it("correctly identifies a directory conflicting files", async () => { + const { root } = useFixture({ fixture: `is-folder-empty` }); + fs.writeFileSync(path.join(root, "README.md"), "my cool project"); + const result = isFolderEmpty(root); + expect(result.isEmpty).toEqual(false); + expect(result.conflicts).toEqual(["README.md"]); + }); +}); diff --git a/packages/create-turbo/__tests__/isWritable.test.ts b/packages/create-turbo/__tests__/isWritable.test.ts new file mode 100644 index 0000000..b06670b --- /dev/null +++ b/packages/create-turbo/__tests__/isWritable.test.ts @@ -0,0 +1,35 @@ +import path from "path"; +import { isWriteable } from "../src/utils/isWriteable"; +import { setupTestFixtures } from "@turbo/test-utils"; +import fs from "fs-extra"; + +describe("isWriteable", () => { + const { useFixture } = setupTestFixtures({ + directory: path.join(__dirname, "../"), + }); + + it("correctly identifies a writeable directory", async () => { + const { root } = useFixture({ fixture: `is-writeable` }); + const result = await isWriteable(root); + expect(result).toEqual(true); + }); + + it("correctly identifies a non-writeable directory", async () => { + const { root } = useFixture({ fixture: `is-writeable` }); + const result = await isWriteable(path.join(root, "does-not-exist")); + expect(result).toEqual(false); + }); + + it("returns false on unexpected failure", async () => { + const { root } = useFixture({ fixture: `is-writeable` }); + const mockFsAccess = jest + .spyOn(fs, "access") + .mockRejectedValue(new Error("unknown error")); + + const result = await isWriteable(root); + expect(result).toEqual(false); + expect(mockFsAccess).toHaveBeenCalledWith(root, fs.constants.W_OK); + + mockFsAccess.mockRestore(); + }); +}); diff --git a/packages/create-turbo/__tests__/test-utils.ts b/packages/create-turbo/__tests__/test-utils.ts new file mode 100644 index 0000000..fa6c204 --- /dev/null +++ b/packages/create-turbo/__tests__/test-utils.ts @@ -0,0 +1,34 @@ +import path from "path"; +import { PackageManager } from "@turbo/workspaces"; + +export function getWorkspaceDetailsMockReturnValue({ + root, + packageManager = "npm", +}: { + root: string; + packageManager: PackageManager; +}) { + return { + name: "mock-project", + packageManager, + paths: { + root, + packageJson: path.join(root, "package.json"), + lockfile: path.join(root, "yarn.lock"), + nodeModules: path.join(root, "node_modules"), + }, + workspaceData: { + globs: ["packages/*"], + workspaces: [ + { + name: "packages/mock-package", + paths: { + root: path.join(root, "packages/mock-package"), + packageJson: path.join(root, "packages/mock-package/package.json"), + nodeModules: path.join(root, "packages/mock-package/node_modules"), + }, + }, + ], + }, + }; +} |
