UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

316 lines (276 loc) 8.98 kB
import { Alepha } from "alepha"; import { cliOptions } from "alepha/command"; import { LogDestinationProvider, MemoryDestinationProvider, } from "alepha/logger"; import { describe, expect, test } from "vitest"; import { ChangelogCommand, changelogOptions, GitMessageParser, GitProvider, } from "../commands/gen/changelog.ts"; // ============================================================================= // MOCK GIT PROVIDER // ============================================================================= let currentMockResponses: Map<string, string> | null = null; class MockGitProvider extends GitProvider { override async exec(cmd: string, _cwd: string): Promise<string> { if (!currentMockResponses) { throw new Error("No git mocks configured"); } const response = currentMockResponses.get(cmd); if (response === undefined) { throw new Error(`Unmocked git command: ${cmd}`); } return response; } } function setMockGitResponses( mocks: Array<{ cmd: string; response: string }>, ): void { currentMockResponses = new Map<string, string>(); for (const { cmd, response } of mocks) { currentMockResponses.set(cmd, response); } } // ============================================================================= // TESTS // ============================================================================= describe("changelog", () => { describe("GitMessageParser.parseCommit", () => { const defaultConfig = { ignore: ["chore", "ci", "build", "test"] }; const getParser = () => { const alepha = Alepha.create(); return alepha.inject(GitMessageParser); }; test("should parse conventional commit with scope", () => { const parser = getParser(); const result = parser.parseCommit( "abc12345 feat(ui): add button component", defaultConfig, ); expect(result).toEqual({ hash: "abc12345", type: "feat", scope: "ui", description: "add button component", breaking: false, }); }); test("should ignore conventional commit without scope", () => { const parser = getParser(); const result = parser.parseCommit( "def45678 fix: resolve memory leak", defaultConfig, ); expect(result).toBeNull(); }); test("should detect breaking change with ! marker", () => { const parser = getParser(); const result = parser.parseCommit( "aaa78901 feat(api)!: remove deprecated endpoint", defaultConfig, ); expect(result).toEqual({ hash: "aaa78901", type: "feat", scope: "api", description: "remove deprecated endpoint", breaking: true, }); }); test("should detect breaking change in description", () => { const parser = getParser(); const result = parser.parseCommit( "bbb01234 feat(auth): breaking change to token format", defaultConfig, ); expect(result).toEqual({ hash: "bbb01234", type: "feat", scope: "auth", description: "breaking change to token format", breaking: true, }); }); test("should ignore commits with ignored scope", () => { const parser = getParser(); const result = parser.parseCommit( "ccc34567 feat(chore): update deps", defaultConfig, ); expect(result).toBeNull(); }); test("should ignore commits with nested ignored scope", () => { const parser = getParser(); const result = parser.parseCommit( "ddd67890 feat(test/unit): add tests", defaultConfig, ); expect(result).toBeNull(); }); test("should ignore module-style commits (no type)", () => { const parser = getParser(); const result = parser.parseCommit("eee90123 cli: add new command", {}); expect(result).toBeNull(); }); test("should return null for non-matching commits", () => { const parser = getParser(); const result = parser.parseCommit( "fff78901 random commit message", defaultConfig, ); expect(result).toBeNull(); }); test("should truncate hash to 8 characters", () => { const parser = getParser(); const result = parser.parseCommit( "abcdef1234567890 feat(core): something", defaultConfig, ); expect(result?.hash).toBe("abcdef12"); }); test("should parse all supported types", () => { const parser = getParser(); const types = ["feat", "fix", "docs", "refactor", "perf", "revert"]; for (const type of types) { const result = parser.parseCommit( `abc12345 ${type}(core): test`, defaultConfig, ); expect(result?.type).toBe(type); } }); test("should handle nested scopes", () => { const parser = getParser(); const result = parser.parseCommit( "abc12345 feat(api/users): add endpoint", defaultConfig, ); expect(result).toEqual({ hash: "abc12345", type: "feat", scope: "api/users", description: "add endpoint", breaking: false, }); }); }); describe("changelogOptions atom", () => { test("should have default ignore list", async () => { const alepha = Alepha.create(); await alepha.start(); const config = alepha.store.get(changelogOptions); expect(config.ignore).toContain("chore"); expect(config.ignore).toContain("ci"); expect(config.ignore).toContain("build"); expect(config.ignore).toContain("test"); }); test("should allow custom configuration", async () => { const alepha = Alepha.create(); alepha.set(changelogOptions, { ignore: ["custom", "internal"], }); await alepha.start(); const config = alepha.store.get(changelogOptions); expect(config.ignore).toEqual(["custom", "internal"]); }); }); describe("changelog command", () => { const setupCommand = async ( gitMocks: { cmd: string; response: string }[], argv: string[] = ["changelog"], options?: { ignore?: string[] }, ) => { setMockGitResponses(gitMocks); const alepha = Alepha.create() .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }) .with({ provide: GitProvider, use: MockGitProvider }) .with(ChangelogCommand); alepha.store.mut(cliOptions, (old) => ({ ...old, argv, })); if (options) { alepha.set(changelogOptions, options); } await alepha.start(); return { alepha, logger: alepha.inject(MemoryDestinationProvider), }; }; test("should show changes since latest tag", async () => { await setupCommand([ { cmd: "tag --sort=-version:refname", response: "1.0.0\n0.9.0\n" }, { cmd: "log 1.0.0..HEAD --oneline", response: [ "abc12345 feat(ui): add dashboard", "def56789 fix(core): resolve crash", ].join("\n"), }, ]); // Outputs to stdout - test verifies no error }); test("should show changes from specified version", async () => { await setupCommand( [ { cmd: "log 0.5.0..HEAD --oneline", response: "abc12345 feat(api): new feature", }, ], ["changelog", "--from=0.5.0"], ); // Outputs to stdout - test verifies no error }); test("should handle no tags found", async () => { await setupCommand([ { cmd: "tag --sort=-version:refname", response: "" }, ]); // Should output "No version tags found" - no error }); test("should handle no changes since tag", async () => { await setupCommand([ { cmd: "tag --sort=-version:refname", response: "1.0.0\n" }, { cmd: "log 1.0.0..HEAD --oneline", response: "" }, ]); // Should output "No changes since 1.0.0" - no error }); test("should filter commits by ignore list", async () => { await setupCommand( [ { cmd: "tag --sort=-version:refname", response: "1.0.0\n" }, { cmd: "log 1.0.0..HEAD --oneline", response: [ "abc12345 feat(ui): add dashboard", "def56789 feat(internal): should be filtered", ].join("\n"), }, ], ["changelog"], { ignore: ["internal"] }, ); // "feat(internal)" is filtered out }); test("should use -f alias for --from", async () => { await setupCommand( [ { cmd: "log 0.8.0..HEAD --oneline", response: "abc12345 feat(cli): new command", }, ], ["changelog", "-f=0.8.0"], ); // Outputs to stdout - test verifies no error }); }); });