Skip to content
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
8 changes: 5 additions & 3 deletions src/cli/commands/add.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import * as path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { Config } from "../../types/config.js";
import { getDefaultConfig } from "../../utils/config.js";
import { addCommand } from "./add.js";

Expand All @@ -11,18 +12,19 @@ const mockMkdir = vi.mocked(mkdir);
const mockWriteFile = vi.mocked(writeFile);
const mockGetDefaultConfig = vi.mocked(getDefaultConfig);

const mockConfig = {
const mockConfig: Config = {
aiRulesDir: ".rulesync",
outputPaths: {
copilot: ".github/instructions",
cursor: ".cursor/rules",
cline: ".clinerules",
claudecode: ".",
roo: ".roo/rules",
geminicli: ".geminicli/rules",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"],
watchEnabled: false,
} as const;
};

describe("addCommand", () => {
beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import * as path from "node:path";
import { getDefaultConfig } from "../../utils/config.js";

/**
Expand Down
10 changes: 6 additions & 4 deletions src/cli/commands/generate.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { generateConfigurations, parseRulesFromDirectory } from "../../core/index.js";
import type { Config } from "../../types/config.js";
import {
fileExists,
getDefaultConfig,
Expand All @@ -20,25 +21,26 @@ const mockWriteFileContent = vi.mocked(writeFileContent);
const mockRemoveDirectory = vi.mocked(removeDirectory);
const mockRemoveClaudeGeneratedFiles = vi.mocked(removeClaudeGeneratedFiles);

const mockConfig = {
const mockConfig: Config = {
aiRulesDir: ".rulesync",
outputPaths: {
copilot: ".github/instructions",
cursor: ".cursor/rules",
cline: ".clinerules",
claudecode: ".",
roo: ".roo/rules",
geminicli: ".geminicli/rules",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"],
watchEnabled: false,
} as const;
};

const mockRules = [
{
filename: "test",
filepath: ".rulesync/test.md",
frontmatter: {
targets: ["*"],
targets: ["*"] as ["*"],
root: true,
description: "Test rule",
globs: ["**/*.ts"],
Expand All @@ -49,7 +51,7 @@ const mockRules = [

const mockOutputs = [
{
tool: "copilot",
tool: "copilot" as const,
filepath: ".github/instructions/test.md",
content: "Generated content",
},
Expand Down
6 changes: 3 additions & 3 deletions src/cli/commands/import.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import * as importer from "../../core/importer";
import { importCommand } from "./import";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as importer from "../../core/importer.js";
import { importCommand } from "./import.js";

vi.mock("../../core/importer");

Expand Down
14 changes: 7 additions & 7 deletions src/cli/commands/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ describe("initCommand", () => {
call[0].includes("overview.md"),
);
expect(overviewCall).toBeDefined();
expect(overviewCall?.[1]).toContain("root: true");
expect(overviewCall?.[1]).toContain('targets: ["*"]');
expect(overviewCall?.[1]).toContain("Project overview");
expect(overviewCall![1]).toContain("root: true");
expect(overviewCall![1]).toContain('targets: ["*"]');
expect(overviewCall![1]).toContain("Project overview");
});

it("should create proper content for non-root files", async () => {
Expand All @@ -97,14 +97,14 @@ describe("initCommand", () => {
call[0].includes("frontend.md"),
);
expect(frontendCall).toBeDefined();
expect(frontendCall?.[1]).toContain("root: false");
expect(frontendCall?.[1]).toContain("Frontend development rules");
expect(frontendCall![1]).toContain("root: false");
expect(frontendCall![1]).toContain("Frontend development rules");

const backendCall = mockWriteFileContent.mock.calls.find((call) =>
call[0].includes("backend.md"),
);
expect(backendCall).toBeDefined();
expect(backendCall?.[1]).toContain("root: false");
expect(backendCall?.[1]).toContain("Backend development rules");
expect(backendCall![1]).toContain("root: false");
expect(backendCall![1]).toContain("Backend development rules");
});
});
12 changes: 7 additions & 5 deletions src/cli/commands/status.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { parseRulesFromDirectory } from "../../core/index.js";
import type { ToolTarget } from "../../types/rules.js";
import { fileExists, getDefaultConfig } from "../../utils/index.js";
import { statusCommand } from "./status.js";

Expand All @@ -18,17 +19,18 @@ const mockConfig = {
cline: ".clinerules",
claudecode: ".",
roo: ".roo/rules",
geminicli: ".",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"],
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"] as ToolTarget[],
watchEnabled: false,
} as const;
};

const mockRules = [
{
filename: "rule1",
filepath: ".rulesync/rule1.md",
frontmatter: {
targets: ["*"],
targets: ["*"] as ["*"],
root: true,
description: "Rule 1",
globs: ["**/*.ts"],
Expand All @@ -40,7 +42,7 @@ const mockRules = [
filename: "rule2",
filepath: ".rulesync/rule2.md",
frontmatter: {
targets: ["copilot", "cursor"],
targets: ["copilot", "cursor"] as ToolTarget[],
root: false,
description: "Rule 2",
globs: ["**/*.js"],
Expand Down Expand Up @@ -149,7 +151,7 @@ describe("statusCommand", () => {
filename: "rule1",
filepath: ".rulesync/rule1.md",
frontmatter: {
targets: ["*"],
targets: ["*"] as ["*"],
root: true,
description: "Rule 1",
globs: ["**/*.ts"],
Expand Down
8 changes: 5 additions & 3 deletions src/cli/commands/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { parseRulesFromDirectory, validateRules } from "../../core/index.js";
import type { ToolTarget } from "../../types/rules.js";
import { fileExists, getDefaultConfig } from "../../utils/index.js";
import { validateCommand } from "./validate.js";

Expand All @@ -19,17 +20,18 @@ const mockConfig = {
cline: ".clinerules",
claudecode: ".",
roo: ".roo/rules",
geminicli: ".",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"],
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"] as ToolTarget[],
watchEnabled: false,
} as const;
};

const mockRules = [
{
filename: "rule1",
filepath: ".rulesync/rule1.md",
frontmatter: {
targets: ["*"],
targets: ["*"] as ["*"],
root: true,
description: "Rule 1",
globs: ["**/*.ts"],
Expand Down
18 changes: 10 additions & 8 deletions src/cli/commands/watch.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FSWatcher } from "chokidar";
import { watch } from "chokidar";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ToolTarget } from "../../types/rules.js";
import { getDefaultConfig } from "../../utils/index.js";
import { generateCommand } from "./generate.js";
import { watchCommand } from "./watch.js";
Expand Down Expand Up @@ -28,10 +29,11 @@ const mockConfig = {
cline: ".clinerules",
claudecode: ".",
roo: ".roo/rules",
geminicli: ".",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"],
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"] as ToolTarget[],
watchEnabled: false,
} as const;
};

// Mock watcher instance
const mockWatcher: MockWatcher = {
Expand All @@ -44,7 +46,7 @@ describe("watchCommand", () => {
vi.clearAllMocks();
mockGetDefaultConfig.mockReturnValue(mockConfig);
mockGenerateCommand.mockResolvedValue();
mockWatch.mockReturnValue(mockWatcher as FSWatcher);
mockWatch.mockReturnValue(mockWatcher as unknown as FSWatcher);

// Mock console methods
vi.spyOn(console, "log").mockImplementation(() => {});
Expand Down Expand Up @@ -95,7 +97,7 @@ describe("watchCommand", () => {
await new Promise((resolve) => setTimeout(resolve, 10));

// Simulate file change
await changeHandler?.("test.md");
await changeHandler!("test.md");

expect(console.log).toHaveBeenCalledWith("\n📝 Detected change in test.md");
expect(mockGenerateCommand).toHaveBeenCalledTimes(2); // Initial + change
Expand All @@ -115,7 +117,7 @@ describe("watchCommand", () => {
await new Promise((resolve) => setTimeout(resolve, 10));

// Simulate file addition
await addHandler?.("new-rule.md");
await addHandler!("new-rule.md");

expect(console.log).toHaveBeenCalledWith("\n📝 Detected change in new-rule.md");
expect(mockGenerateCommand).toHaveBeenCalledTimes(2); // Initial + add
Expand All @@ -134,7 +136,7 @@ describe("watchCommand", () => {
await new Promise((resolve) => setTimeout(resolve, 10));

// Simulate file deletion
await unlinkHandler?.("deleted-rule.md");
await unlinkHandler!("deleted-rule.md");

expect(console.log).toHaveBeenCalledWith("\n🗑️ Removed deleted-rule.md");
expect(mockGenerateCommand).toHaveBeenCalledTimes(2); // Initial + unlink
Expand All @@ -160,7 +162,7 @@ describe("watchCommand", () => {
await new Promise((resolve) => setTimeout(resolve, 50));

// Simulate file change with error
await changeHandler?.("test.md");
await changeHandler!("test.md");

expect(console.error).toHaveBeenCalledWith("❌ Failed to regenerate:", error);
});
Expand All @@ -182,7 +184,7 @@ describe("watchCommand", () => {
await new Promise((resolve) => setTimeout(resolve, 50));

// Simulate watcher error
errorHandler?.(error);
errorHandler!(error);

expect(console.error).toHaveBeenCalledWith("❌ Watcher error:", error);
});
Expand Down
12 changes: 7 additions & 5 deletions src/core/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const mockConfig: Config = {
cursor: ".cursor/rules",
cline: ".clinerules",
claudecode: ".",
geminicli: ".geminicli",
roo: ".roo",
},
defaultTargets: ["copilot", "cursor", "cline", "claudecode"],
watchEnabled: false,
Expand Down Expand Up @@ -80,11 +82,11 @@ describe("generateConfigurations", () => {
const outputs = await generateConfigurations(mockRules, mockConfig, ["claudecode"]);

expect(outputs.length).toBeGreaterThan(0);
expect(outputs[0].tool).toBe("claudecode");
expect(outputs[0].filepath).toBe("CLAUDE.md");
expect(outputs[0].content).toContain("This is a test rule");
expect(outputs[0].content).toContain("@.claude/memories/claudecode-only.md");
expect(outputs[0].content).not.toContain("This is a copilot only rule");
expect(outputs[0]!.tool).toBe("claudecode");
expect(outputs[0]!.filepath).toBe("CLAUDE.md");
expect(outputs[0]!.content).toContain("This is a test rule");
expect(outputs[0]!.content).toContain("@.claude/memories/claudecode-only.md");
expect(outputs[0]!.content).not.toContain("This is a copilot only rule");
});

it("should handle empty rules gracefully", async () => {
Expand Down
17 changes: 9 additions & 8 deletions src/core/importer.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as parsers from "../parsers";
import { importConfiguration } from "./importer";
import * as parsers from "../parsers/index.js";
import type { ToolTarget } from "../types/index.js";
import { importConfiguration } from "./importer.js";

vi.mock("../parsers");

Expand Down Expand Up @@ -36,7 +37,7 @@ describe("importConfiguration", () => {
{
frontmatter: {
root: false,
targets: ["claudecode"] as const,
targets: ["claudecode"] as ToolTarget[],
description: "Main config",
globs: ["**/*"],
},
Expand All @@ -49,8 +50,8 @@ describe("importConfiguration", () => {
vi.spyOn(parsers, "parseClaudeConfiguration").mockResolvedValueOnce({
rules: mockRules,
errors: [],
ignorePatterns: undefined,
mcpServers: undefined,
ignorePatterns: [],
mcpServers: {},
});

const result = await importConfiguration({
Expand All @@ -72,7 +73,7 @@ describe("importConfiguration", () => {
{
frontmatter: {
root: false,
targets: ["cursor"] as const,
targets: ["cursor"] as ToolTarget[],
description: "Rule 1",
globs: ["**/*"],
},
Expand All @@ -83,7 +84,7 @@ describe("importConfiguration", () => {
{
frontmatter: {
root: false,
targets: ["cursor"] as const,
targets: ["cursor"] as ToolTarget[],
description: "Rule 2",
globs: ["**/*"],
},
Expand Down Expand Up @@ -238,7 +239,7 @@ describe("importConfiguration", () => {
{
frontmatter: {
root: false,
targets: ["claudecode"] as const,
targets: ["claudecode"] as ToolTarget[],
description: "Test",
globs: ["**/*"],
},
Expand Down
Loading