import path from "node:path"; import type { Changeset } from "@changesets/types"; import writeChangeset from "@changesets/write"; import { createFixture } from "fs-fixture"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { Git } from "./git.ts"; import { setupOctokit } from "./octokit.ts"; import { runVersion } from "./run.ts"; vi.mock("@actions/github", () => ({ context: { repo: { owner: "changesets", repo: "action", }, ref: "refs/heads/some-branch", sha: "xeac7", }, getOctokit: () => ({ rest: mockedGithubMethods, graphql: mockedGraphql, }), })); vi.mock("./git.ts"); vi.mock("@changesets/ghcommit/git"); let mockedGithubMethods = { pulls: { create: vi.fn(), list: vi.fn(), }, repos: { createRelease: vi.fn(), }, }; let mockedGraphql = vi.fn(); const nodeModulesDir = path.join(import.meta.dirname, "..", "node_modules"); function createSimpleProjectFixture() { return createFixture({ node_modules: (api) => api.symlink(nodeModulesDir), ".changeset/config.json": JSON.stringify({}), "packages/pkg-a/package.json": JSON.stringify({ name: "changesets-dev-simple-project-pkg-a", version: "1.0.0", dependencies: { "changesets-dev-simple-project-pkg-b": "1.0.0", }, }), "packages/pkg-b/package.json": JSON.stringify({ name: "changesets-dev-simple-project-pkg-b", version: "1.0.0", }), "package.json": JSON.stringify({ name: "simple-project", version: "1.0.0", private: true, workspaces: ["packages/*"], }), }); } function createIgnoredPackageFixture() { return createFixture({ node_modules: (api) => api.symlink(nodeModulesDir), ".changeset/config.json": JSON.stringify({ ignore: ["changesets-dev-ignored-package-pkg-a"], }), "packages/pkg-a/package.json": JSON.stringify({ name: "changesets-dev-ignored-package-pkg-a", version: "1.0.0", dependencies: { "changesets-dev-ignored-package-pkg-b": "1.0.0", }, }), "packages/pkg-b/package.json": JSON.stringify({ name: "changesets-dev-ignored-package-pkg-b", version: "1.0.0", }), "package.json": JSON.stringify({ name: "ignored-package", version: "1.0.0", private: true, workspaces: ["packages/*"], }), }); } const writeChangesets = (changesets: Changeset[], cwd: string) => { return Promise.all(changesets.map((commit) => writeChangeset(commit, cwd))); }; beforeEach(() => { vi.clearAllMocks(); }); describe("version", () => { it("creates simple PR", async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, { name: "changesets-dev-simple-project-pkg-b", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); }); it('creates a draft PR when prDraft is "create"', async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, prDraft: "create", }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); }); it("only includes bumped packages in the PR body", async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); }); it("doesn't include ignored package that got a dependency update in the PR body", async () => { await using fixture = await createIgnoredPackageFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-ignored-package-pkg-b", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); }); it("does not include changelog entries if full message exceeds size limit", async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: `# Non manus superum ## Nec cornibus aequa numinis multo onerosior adde Lorem markdownum undas consumpserat malas, nec est lupus; memorant gentisque ab limine auctore. Eatque et promptu deficit, quam videtur aequa est **faciat**, locus. Potentia deus habebat pia quam qui coniuge frater, tibi habent fertque viribus. E et cognoscere arcus, lacus aut sic pro crimina fuit tum **auxilium** dictis, qua, in. In modo. Nomen illa membra. > Corpora gratissima parens montibus tum coeperat qua remulus caelum Helenamque? > Non poenae modulatur Amathunta in concita superi, procerum pariter rapto cornu > munera. Perrhaebum parvo manus contingere, morari, spes per totiens ut > dividite proculcat facit, visa. Adspicit sequitur diffamatamque superi Phoebo qua quin lammina utque: per? Exit decus aut hac inpia, seducta mirantia extremo. Vidi pedes vetus. Saturnius fluminis divesque vulnere aquis parce lapsis rabie si visa fulmineis. `, }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, prBodyMaxCharacters: 1000, }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); expect(mockedGithubMethods.pulls.create.mock.calls[0][0].body).toMatch( /The changelog information of each package has been omitted from this message/, ); }); it("does not include any release information if a message with simplified release info exceeds size limit", async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ data: { number: 123 }, })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: `# Non manus superum ## Nec cornibus aequa numinis multo onerosior adde Lorem markdownum undas consumpserat malas, nec est lupus; memorant gentisque ab limine auctore. Eatque et promptu deficit, quam videtur aequa est **faciat**, locus. Potentia deus habebat pia quam qui coniuge frater, tibi habent fertque viribus. E et cognoscere arcus, lacus aut sic pro crimina fuit tum **auxilium** dictis, qua, in. In modo. Nomen illa membra. > Corpora gratissima parens montibus tum coeperat qua remulus caelum Helenamque? > Non poenae modulatur Amathunta in concita superi, procerum pariter rapto cornu > munera. Perrhaebum parvo manus contingere, morari, spes per totiens ut > dividite proculcat facit, visa. Adspicit sequitur diffamatamque superi Phoebo qua quin lammina utque: per? Exit decus aut hac inpia, seducta mirantia extremo. Vidi pedes vetus. Saturnius fluminis divesque vulnere aquis parce lapsis rabie si visa fulmineis. `, }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, prBodyMaxCharacters: 500, }); expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); expect(mockedGithubMethods.pulls.create.mock.calls[0][0].body).toMatch( /All release information have been omitted from this message, as the content exceeds the size limit/, ); }); it('updates an existing PR via GraphQL without converting it to draft when prDraft is "create"', async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [{ number: 123, node_id: "PR_kwDOA" }], })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, prDraft: "create", }); expect(mockedGraphql.mock.calls[0]).toMatchSnapshot(); }); it('updates an existing PR via GraphQL and converts it to draft when prDraft is "always"', async () => { await using fixture = await createSimpleProjectFixture(); const cwd = fixture.path; mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [{ number: 123, node_id: "PR_kwDOA" }], })); await writeChangesets( [ { releases: [ { name: "changesets-dev-simple-project-pkg-a", type: "minor", }, ], summary: "Awesome feature", }, ], cwd, ); await runVersion({ octokit: setupOctokit("@@GITHUB_TOKEN"), githubToken: "@@GITHUB_TOKEN", git: new Git({ cwd }), cwd, prDraft: "always", }); expect(mockedGraphql.mock.calls[0]).toMatchSnapshot(); }); });