Skip to content

Commit 220211e

Browse files
committed
Simplify and update unit tests
1 parent bf9f75c commit 220211e

File tree

3 files changed

+77
-122
lines changed

3 files changed

+77
-122
lines changed

lib/src/normalizer.test.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -106,50 +106,6 @@ describe("normalizeConfig", () => {
106106
]);
107107
});
108108

109-
it("fileFilter includes allowed files and excludes others", async () => {
110-
const result = await normalizeConfig({
111-
include: ["src/**"],
112-
exclude: ["src/ignore/**"],
113-
matcher: "micromatch",
114-
});
115-
expect(result.fileFilter("src/index.ts")).toBe(true);
116-
expect(result.fileFilter("src/ignore/file.ts")).toBe(false);
117-
expect(result.fileFilter("other/file.ts")).toBe(false);
118-
});
119-
120-
it("fileFilter includes allowed files and excludes others", async () => {
121-
const result = await normalizeConfig({
122-
include: ["src/**"],
123-
exclude: ["src/ignore/**"],
124-
});
125-
expect(result.fileFilter("src/index.ts")).toBe(true);
126-
expect(result.fileFilter("src/ignore/file.ts")).toBe(false);
127-
expect(result.fileFilter("other/file.ts")).toBe(false);
128-
});
129-
130-
it("fileFilter includes allowed files and excludes others", async () => {
131-
const result = await normalizeConfig({
132-
include: ["**/package.json"],
133-
exclude: ["package.json", "**/dist/**"],
134-
});
135-
expect(result.fileFilter("pkgs/src/package.json")).toBe(true);
136-
expect(result.fileFilter("pkgs\\src\\package.json")).toBe(true);
137-
expect(result.fileFilter("package.json")).toBe(false);
138-
expect(result.fileFilter("other/file.ts")).toBe(false);
139-
});
140-
141-
it("fileFilter includes allowed files and excludes others", async () => {
142-
const result = await normalizeConfig({
143-
include: ["src/**/*.json"],
144-
exclude: ["src/ignore/**"],
145-
});
146-
expect(result.fileFilter("src/index.ts")).toBe(false);
147-
expect(result.fileFilter("src/index.json")).toBe(true);
148-
expect(result.fileFilter("src/abc/index.json")).toBe(true);
149-
expect(result.fileFilter("src/ignore/file.ts")).toBe(false);
150-
expect(result.fileFilter("other/file.json")).toBe(false);
151-
});
152-
153109
it("uses basicMatcher by default", async () => {
154110
const result = await normalizeConfig({});
155111
expect(result.matcher).toBe(basicMatcher);

lib/src/utils.test.ts

Lines changed: 63 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,85 @@
11
import { describe, it, expect, beforeAll, afterAll } from "vitest";
22
import fs from "node:fs/promises";
33
import path from "node:path";
4-
import os from "node:os";
5-
import { listMatchingFiles } from "./utils";
6-
7-
const conflictContent = `
8-
{
9-
<<<<<<< ours
10-
"value": 1
11-
=======
12-
"value": 2
13-
>>>>>>> theirs
14-
}
15-
`;
16-
17-
const cleanContent = `
18-
{
19-
"value": 42
20-
}
21-
`;
22-
23-
describe("collectFiles", () => {
24-
let tmpDir: string;
25-
let conflictedFile: string;
26-
let cleanFile: string;
27-
let ignoredFile: string;
28-
29-
beforeAll(async () => {
30-
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "collect-files-"));
4+
import { hasConflict, createSkipDirectoryMatcher, listMatchingFiles, backupFile } from "./utils"; // adjust import
5+
import { basicMatcher } from "./matcher";
6+
7+
const matcher = basicMatcher;
8+
9+
const TMP = path.join(process.cwd(), "tmp-test");
10+
11+
beforeAll(async () => {
12+
await fs.rm(TMP, { recursive: true, force: true });
13+
await fs.mkdir(TMP, { recursive: true });
14+
15+
// Create dirs & files
16+
await fs.writeFile(path.join(TMP, "clean.txt"), "hello");
17+
await fs.writeFile(
18+
path.join(TMP, "conflict.txt"),
19+
"line1\n<<<<<<< ours\nconflict\n=======\ntheirs\n>>>>>>>",
20+
);
21+
await fs.mkdir(path.join(TMP, "src"));
22+
await fs.writeFile(path.join(TMP, "src", "index.ts"), "console.log('ok')");
23+
await fs.mkdir(path.join(TMP, "node_modules"));
24+
await fs.writeFile(path.join(TMP, "node_modules", "ignore.js"), "ignored");
25+
});
3126

32-
conflictedFile = path.join(tmpDir, "conflicted.json");
33-
cleanFile = path.join(tmpDir, "clean.json");
34-
ignoredFile = path.join(tmpDir, "ignored.txt");
27+
afterAll(async () => {
28+
await fs.rm(TMP, { recursive: true, force: true });
29+
});
3530

36-
await fs.writeFile(conflictedFile, conflictContent, "utf8");
37-
await fs.writeFile(cleanFile, cleanContent, "utf8");
38-
await fs.writeFile(ignoredFile, cleanContent, "utf8");
31+
describe("hasConflict", () => {
32+
it("detects conflict markers", () => {
33+
expect(hasConflict("<<<<<<<\n=======\n>>>>>>>")).toBe(true);
3934
});
40-
41-
afterAll(async () => {
42-
await fs.rm(tmpDir, { recursive: true, force: true });
35+
it("returns false for clean content", () => {
36+
expect(hasConflict("no conflicts here")).toBe(false);
4337
});
38+
});
4439

45-
it("collects only conflicted files by default", async () => {
40+
describe("listMatchingFiles", () => {
41+
it("finds only conflicted files by default", async () => {
4642
const files = await listMatchingFiles({
47-
root: tmpDir,
48-
fileFilter: file => file.endsWith(".json"),
43+
root: TMP,
44+
include: ["**/*.txt"],
45+
exclude: ["node_modules/**"],
46+
matcher,
4947
});
50-
51-
const paths = files.map(f => f.filePath);
52-
53-
expect(paths).toContain(conflictedFile);
54-
expect(paths).not.toContain(cleanFile);
55-
expect(paths).not.toContain(ignoredFile);
56-
57-
// check content as well
58-
const conflicted = files.find(f => f.filePath === conflictedFile);
59-
expect(conflicted?.content).toContain("<<<<<<<");
48+
const names = files.map(f => path.basename(f.filePath));
49+
expect(names).toContain("conflict.txt");
50+
expect(names).not.toContain("clean.txt");
6051
});
6152

62-
it("collects conflicted + clean files when includeNonConflicted is true", async () => {
53+
it("includes non-conflicted if option enabled", async () => {
6354
const files = await listMatchingFiles({
64-
root: tmpDir,
65-
fileFilter: file => file.endsWith(".json"),
55+
root: TMP,
56+
include: ["**/*.txt"],
57+
exclude: [],
58+
matcher,
6659
includeNonConflicted: true,
6760
});
68-
69-
const paths = files.map(f => f.filePath);
70-
71-
expect(paths).toContain(conflictedFile);
72-
expect(paths).toContain(cleanFile);
73-
expect(paths).not.toContain(ignoredFile);
74-
75-
const clean = files.find(f => f.filePath === cleanFile);
76-
expect(clean?.content).toContain('"value": 42');
61+
const names = files.map(f => path.basename(f.filePath));
62+
expect(names).toEqual(expect.arrayContaining(["clean.txt", "conflict.txt"]));
7763
});
7864

79-
it("skips files that do not match fileFilter", async () => {
65+
it("only files - no dirs", async () => {
8066
const files = await listMatchingFiles({
81-
root: tmpDir,
82-
fileFilter: file => file.endsWith(".json"),
67+
root: TMP,
68+
include: ["*.txt"],
69+
exclude: [],
70+
matcher,
8371
});
72+
const names = files.map(f => path.basename(f.filePath));
73+
expect(names).toEqual(expect.arrayContaining(["conflict.txt"]));
74+
});
75+
});
8476

85-
const paths = files.map(f => f.filePath);
86-
expect(paths).not.toContain(ignoredFile);
77+
describe("backupFile", () => {
78+
it("creates backup copy under .merge-backups", async () => {
79+
const src = path.join(TMP, "clean.txt");
80+
const backup = await backupFile(src);
81+
const exists = await fs.readFile(backup, "utf8");
82+
expect(exists).toBe("hello");
83+
expect(backup).toMatch(/\.merge-backups/);
8784
});
8885
});

lib/src/utils.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,7 @@ export const listMatchingFiles = async (options: CollectFilesOptions): Promise<F
4747
return matcher.isMatch(posixPath, include) && !matcher.isMatch(posixPath, exclude);
4848
};
4949

50-
const [keepDirs, dropDirs] = deriveDirExcludes(include, exclude);
51-
52-
const skipDirMatcher = (dirPath: string) => {
53-
const posixPath = dirPath.replace(/\\/g, "/");
54-
return (
55-
matcher.isMatch(posixPath, dropDirs) ||
56-
(keepDirs.length > 0 && !matcher.isMatch(posixPath, keepDirs))
57-
);
58-
};
50+
const skipDirMatcher = createSkipDirectoryMatcher(include, exclude, matcher);
5951

6052
const fileEntries: FileEntry[] = [];
6153

@@ -104,10 +96,14 @@ export const listMatchingFiles = async (options: CollectFilesOptions): Promise<F
10496
* Derive directory pruning patterns from include/exclude rules.
10597
* These patterns are used to avoid walking unnecessary directories.
10698
*/
107-
export const deriveDirExcludes = (include: string[], exclude: string[]): [string[], string[]] => {
99+
export const createSkipDirectoryMatcher = (
100+
include: string[],
101+
exclude: string[],
102+
matcher: NormalizedConfig["matcher"],
103+
) => {
108104
// ---- Case 1: includes are only root-level files → prune all dirs
109105
if (include.length > 0 && include.every(p => !p.includes("/") && !p.includes("**"))) {
110-
return [[], ["**"]]; // minimal: just exclude all dirs
106+
return () => false; // minimal: just exclude all dirs
111107
}
112108

113109
const keepDirs = new Set<string>();
@@ -133,7 +129,13 @@ export const deriveDirExcludes = (include: string[], exclude: string[]): [string
133129
}
134130
}
135131

136-
return [[...keepDirs], [...dropDirs]];
132+
return (dirPath: string) => {
133+
const posixPath = dirPath.replace(/\\/g, "/");
134+
return (
135+
matcher.isMatch(posixPath, [...dropDirs]) ||
136+
(keepDirs.size > 0 && !matcher.isMatch(posixPath, [...keepDirs]))
137+
);
138+
};
137139
};
138140

139141
export const backupFile = async (filePath: string, backupDir = ".merge-backups") => {

0 commit comments

Comments
 (0)