mcpb/test/dxtignore.test.ts
2025-06-28 16:11:32 -07:00

317 lines
11 KiB
TypeScript

import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import {
getAllFiles,
readDxtIgnorePatterns,
shouldExclude,
} from "../src/node/files.js";
describe("DxtIgnore functionality", () => {
let tempDir: string;
let dxtIgnorePath: string;
beforeEach(() => {
// Create a temp directory for each test
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "dxt-test-"));
dxtIgnorePath = path.join(tempDir, ".dxtignore");
});
afterEach(() => {
// Clean up temp directory after each test
fs.rmSync(tempDir, { recursive: true, force: true });
});
describe("readDxtIgnorePatterns", () => {
it("should return empty array when .dxtignore doesn't exist", () => {
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual([]);
});
it("should read patterns from .dxtignore file", () => {
const content = `*.log
node_modules/
temp/
.env`;
fs.writeFileSync(dxtIgnorePath, content);
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual(["*.log", "node_modules/", "temp/", ".env"]);
});
it("should ignore empty lines and comments", () => {
const content = `# Comment line
*.log
# Another comment
node_modules/
.env`;
fs.writeFileSync(dxtIgnorePath, content);
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual(["*.log", "node_modules/", ".env"]);
});
it("should trim whitespace from lines", () => {
const content = ` *.log
node_modules/
temp/`;
fs.writeFileSync(dxtIgnorePath, content);
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual(["*.log", "node_modules/", "temp/"]);
});
it("should handle Windows line endings", () => {
const content = `*.log\r\nnode_modules/\r\ntemp/`;
fs.writeFileSync(dxtIgnorePath, content);
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual(["*.log", "node_modules/", "temp/"]);
});
it("should return empty array if file cannot be read", () => {
const content = "*.log";
fs.writeFileSync(dxtIgnorePath, content);
fs.chmodSync(dxtIgnorePath, 0); // Make file unreadable
const patterns = readDxtIgnorePatterns(tempDir);
expect(patterns).toEqual([]);
// Restore permissions for cleanup
fs.chmodSync(dxtIgnorePath, 0o644);
});
});
describe("shouldExclude with additional patterns", () => {
it("should exclude files matching additional patterns", () => {
const additionalPatterns = ["*.test.js", "coverage/"];
expect(shouldExclude("test.test.js", additionalPatterns)).toBe(true);
expect(shouldExclude("coverage/file.txt", additionalPatterns)).toBe(true);
expect(shouldExclude("src/index.js", additionalPatterns)).toBe(false);
});
it("should still exclude default patterns", () => {
const additionalPatterns = ["*.test.js"];
// Test default pattern
expect(shouldExclude(".DS_Store", additionalPatterns)).toBe(true);
// Test additional pattern
expect(shouldExclude("test.test.js", additionalPatterns)).toBe(true);
// Test non-excluded file
expect(shouldExclude("src/index.js", additionalPatterns)).toBe(false);
});
it("should handle glob patterns correctly", () => {
const additionalPatterns = ["*.log", "temp/*"];
expect(shouldExclude("debug.log", additionalPatterns)).toBe(true);
expect(shouldExclude("temp/file.txt", additionalPatterns)).toBe(true);
expect(shouldExclude("src/main.js", additionalPatterns)).toBe(false);
});
it("should handle exact path matches", () => {
const additionalPatterns = ["node_modules", "dist"];
expect(shouldExclude("node_modules", additionalPatterns)).toBe(true);
expect(shouldExclude("dist", additionalPatterns)).toBe(true);
expect(shouldExclude("src/node_modules", additionalPatterns)).toBe(true);
expect(shouldExclude("src", additionalPatterns)).toBe(false);
});
});
describe("getAllFiles with .dxtignore", () => {
let testStructure: string;
beforeEach(() => {
// Create test directory structure
testStructure = path.join(tempDir, "test-extension");
fs.mkdirSync(testStructure);
fs.mkdirSync(path.join(testStructure, "src"));
fs.mkdirSync(path.join(testStructure, "tests"));
fs.mkdirSync(path.join(testStructure, "coverage"));
fs.mkdirSync(path.join(testStructure, "logs"));
// Create test files
fs.writeFileSync(path.join(testStructure, "manifest.json"), "{}");
fs.writeFileSync(
path.join(testStructure, "src/index.js"),
"console.log('hi')",
);
fs.writeFileSync(path.join(testStructure, "tests/test.js"), "test()");
fs.writeFileSync(
path.join(testStructure, "coverage/report.txt"),
"coverage",
);
fs.writeFileSync(path.join(testStructure, "logs/debug.log"), "log");
fs.writeFileSync(path.join(testStructure, "debug.log"), "log");
});
it("should exclude files matching .dxtignore patterns", () => {
// Create .dxtignore
const dxtIgnoreContent = `*.log
tests/
coverage/`;
fs.writeFileSync(
path.join(testStructure, ".dxtignore"),
dxtIgnoreContent,
);
const patterns = readDxtIgnorePatterns(testStructure);
const files = getAllFiles(testStructure, testStructure, {}, patterns);
const fileNames = Object.keys(files);
// Should include
expect(fileNames).toContain("manifest.json");
expect(fileNames).toContain("src/index.js");
// Should exclude
expect(fileNames).not.toContain("debug.log");
expect(fileNames).not.toContain("logs/debug.log");
expect(fileNames).not.toContain("tests/test.js");
expect(fileNames).not.toContain("coverage/report.txt");
expect(fileNames).not.toContain(".dxtignore");
});
it("should exclude both default patterns and .dxtignore patterns", () => {
// Create .dxtignore
const dxtIgnoreContent = `tests/`;
fs.writeFileSync(
path.join(testStructure, ".dxtignore"),
dxtIgnoreContent,
);
// Create a .git file (default exclusion)
fs.writeFileSync(path.join(testStructure, ".gitignore"), "ignore");
const patterns = readDxtIgnorePatterns(testStructure);
const files = getAllFiles(testStructure, testStructure, {}, patterns);
const fileNames = Object.keys(files);
// Should include
expect(fileNames).toContain("manifest.json");
expect(fileNames).toContain("src/index.js");
// Should exclude (from .dxtignore)
expect(fileNames).not.toContain("tests/test.js");
// Should exclude (from default patterns)
expect(fileNames).not.toContain(".gitignore");
});
it("should work without .dxtignore file", () => {
const files = getAllFiles(testStructure, testStructure, {}, []);
const fileNames = Object.keys(files);
// Should include all files except default exclusions
expect(fileNames).toContain("manifest.json");
expect(fileNames).toContain("src/index.js");
expect(fileNames).toContain("tests/test.js");
expect(fileNames).toContain("coverage/report.txt");
// Note: *.log files are excluded by default, so these won't be included
expect(fileNames).not.toContain("logs/debug.log");
expect(fileNames).not.toContain("debug.log");
});
});
describe("Pattern matching edge cases", () => {
it("should handle nested directories", () => {
const additionalPatterns = ["node_modules/"];
expect(
shouldExclude("node_modules/package/index.js", additionalPatterns),
).toBe(true);
expect(
shouldExclude("src/node_modules/package/index.js", additionalPatterns),
).toBe(true);
});
it("should handle paths with directory patterns", () => {
const additionalPatterns = ["tests/"];
expect(shouldExclude("tests/unit/test.js", additionalPatterns)).toBe(
true,
);
expect(
shouldExclude("tests/integration/test.js", additionalPatterns),
).toBe(true);
// Note: "tests" appears anywhere in path due to current implementation
expect(shouldExclude("src/tests/test.js", additionalPatterns)).toBe(true);
// But paths without "tests" should not be excluded
expect(shouldExclude("src/main.js", additionalPatterns)).toBe(false);
});
it("should match patterns (implementation details)", () => {
const additionalPatterns = ["specific-file.LOG"];
// Test that the pattern is matched
expect(shouldExclude("specific-file.LOG", additionalPatterns)).toBe(true);
// Test completely different file name
expect(shouldExclude("totally-different.txt", additionalPatterns)).toBe(
false,
);
});
});
describe("Negation patterns", () => {
it("should include files that match negation patterns", () => {
const additionalPatterns = ["*.log", "!important.log"];
// debug.log should be excluded (matches *.log, no negation)
expect(shouldExclude("debug.log", additionalPatterns)).toBe(true);
// important.log should NOT be excluded (matches *.log, but negated by !important.log)
expect(shouldExclude("important.log", additionalPatterns)).toBe(false);
// Other files should not be excluded
expect(shouldExclude("main.js", additionalPatterns)).toBe(false);
});
it("should handle multiple negation patterns", () => {
const additionalPatterns = ["*.log", "!important.log", "!critical.log"];
expect(shouldExclude("debug.log", additionalPatterns)).toBe(true);
expect(shouldExclude("important.log", additionalPatterns)).toBe(false);
expect(shouldExclude("critical.log", additionalPatterns)).toBe(false);
expect(shouldExclude("other.log", additionalPatterns)).toBe(true);
});
it("should handle negation with glob patterns", () => {
const additionalPatterns = ["temp/*", "!temp/important.txt"];
expect(shouldExclude("temp/debug.txt", additionalPatterns)).toBe(true);
expect(shouldExclude("temp/important.txt", additionalPatterns)).toBe(
false,
);
expect(shouldExclude("other/file.txt", additionalPatterns)).toBe(false);
});
it("should handle negation with directory patterns", () => {
const additionalPatterns = ["tests/", "!tests/critical/"];
expect(shouldExclude("tests/unit/test.js", additionalPatterns)).toBe(
true,
);
expect(shouldExclude("tests/critical/test.js", additionalPatterns)).toBe(
false,
);
expect(shouldExclude("src/main.js", additionalPatterns)).toBe(false);
});
it("should not negate files that don't match exclusion patterns", () => {
const additionalPatterns = ["*.log", "!main.js"];
// main.js should not be excluded (doesn't match *.log, negation doesn't apply)
expect(shouldExclude("main.js", additionalPatterns)).toBe(false);
// debug.log should be excluded (matches *.log, no applicable negation)
expect(shouldExclude("debug.log", additionalPatterns)).toBe(true);
});
});
});