kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { afterEach, describe, expect, it, vi } from "vitest";
2import type { GitHubConfig } from "../../../../../apps/api/src/plugins/github/config";
3import {
4 createBranchRegex,
5 extractTaskNumber,
6 extractTaskNumberFromBranch,
7 extractTaskNumberFromPRBody,
8 extractTaskNumberFromPRTitle,
9 generateBranchName,
10} from "../../../../../apps/api/src/plugins/github/utils/branch-matcher";
11
12const baseConfig: GitHubConfig = {
13 repositoryOwner: "kaneo",
14 repositoryName: "api",
15 installationId: 1,
16 branchPattern: "{slug}-{number}",
17};
18
19afterEach(() => {
20 vi.restoreAllMocks();
21});
22
23describe("generateBranchName", () => {
24 it("fills placeholders and slugifies the title", () => {
25 expect(
26 generateBranchName(
27 "feature/{slug}-{number}-{title}",
28 "KAN",
29 42,
30 "Fix login: SSO + invites",
31 ),
32 ).toBe("feature/kan-42-fix-login-sso-invites");
33 });
34});
35
36describe("createBranchRegex", () => {
37 it("matches default patterns and optional suffixes", () => {
38 const regex = createBranchRegex("{slug}-{number}-{title}", "KAN");
39
40 expect(regex.test("kan-7-polish-sidebar")).toBe(true);
41 expect(regex.test("kan-7-polish-sidebar-part-2")).toBe(true);
42 expect(regex.test("ops-7-polish-sidebar")).toBe(false);
43 });
44});
45
46describe("extractTaskNumberFromBranch", () => {
47 it("uses the default branch pattern", () => {
48 expect(
49 extractTaskNumberFromBranch("kan-17-refine-search", baseConfig, "KAN"),
50 ).toBe(17);
51 });
52
53 it("supports custom regex patterns", () => {
54 expect(
55 extractTaskNumberFromBranch(
56 "feature/TASK-33",
57 {
58 ...baseConfig,
59 customBranchRegex: "TASK-(\\d+)",
60 },
61 "KAN",
62 ),
63 ).toBe(33);
64 });
65
66 it("returns null and logs when the custom regex is invalid", () => {
67 const consoleError = vi
68 .spyOn(console, "error")
69 .mockImplementation(() => undefined);
70
71 expect(
72 extractTaskNumberFromBranch(
73 "feature/TASK-33",
74 {
75 ...baseConfig,
76 customBranchRegex: "(",
77 },
78 "KAN",
79 ),
80 ).toBeNull();
81 expect(consoleError).toHaveBeenCalledOnce();
82 });
83});
84
85describe("extractTaskNumberFromPRTitle", () => {
86 it("recognizes supported title formats", () => {
87 expect(extractTaskNumberFromPRTitle("[12] Ship notifications")).toBe(12);
88 expect(extractTaskNumberFromPRTitle("Fix sidebar (#34)")).toBe(34);
89 expect(extractTaskNumberFromPRTitle("55: tidy auth flow")).toBe(55);
90 });
91});
92
93describe("extractTaskNumberFromPRBody", () => {
94 it("recognizes task references in the body", () => {
95 expect(extractTaskNumberFromPRBody("Closes #21")).toBe(21);
96 expect(extractTaskNumberFromPRBody("task: 77")).toBe(77);
97 expect(extractTaskNumberFromPRBody("No linked task")).toBeNull();
98 });
99});
100
101describe("extractTaskNumber", () => {
102 it("prefers the branch match before title and body", () => {
103 expect(
104 extractTaskNumber(
105 "kan-88-polish-editor",
106 "[12] Ship notifications",
107 "Closes #21",
108 baseConfig,
109 "KAN",
110 ),
111 ).toBe(88);
112 });
113
114 it("falls back to the title and then body", () => {
115 expect(
116 extractTaskNumber(
117 "misc-branch",
118 "[12] Ship notifications",
119 "Closes #21",
120 baseConfig,
121 "KAN",
122 ),
123 ).toBe(12);
124
125 expect(
126 extractTaskNumber(
127 "misc-branch",
128 undefined,
129 "Resolves task 21",
130 baseConfig,
131 "KAN",
132 ),
133 ).toBe(21);
134 });
135
136 it("accepts task number 0 from the branch before other matches", () => {
137 expect(
138 extractTaskNumber(
139 "kan-0-initial-setup",
140 "[99] Other task",
141 undefined,
142 baseConfig,
143 "KAN",
144 ),
145 ).toBe(0);
146 });
147});