@gitwand/core 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/__tests__/bench.bench.d.ts +14 -0
- package/dist/__tests__/bench.bench.d.ts.map +1 -0
- package/dist/__tests__/bench.bench.js +137 -0
- package/dist/__tests__/bench.bench.js.map +1 -0
- package/dist/__tests__/confidence-v14.test.d.ts +13 -0
- package/dist/__tests__/confidence-v14.test.d.ts.map +1 -0
- package/dist/__tests__/confidence-v14.test.js +284 -0
- package/dist/__tests__/confidence-v14.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +317 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/corpus.d.ts +36 -0
- package/dist/__tests__/corpus.d.ts.map +1 -0
- package/dist/__tests__/corpus.js +541 -0
- package/dist/__tests__/corpus.js.map +1 -0
- package/dist/__tests__/corpus.test.d.ts +17 -0
- package/dist/__tests__/corpus.test.d.ts.map +1 -0
- package/dist/__tests__/corpus.test.js +179 -0
- package/dist/__tests__/corpus.test.js.map +1 -0
- package/dist/__tests__/diff.test.d.ts +10 -0
- package/dist/__tests__/diff.test.d.ts.map +1 -0
- package/dist/__tests__/diff.test.js +178 -0
- package/dist/__tests__/diff.test.js.map +1 -0
- package/dist/__tests__/format-resolvers.test.d.ts +2 -0
- package/dist/__tests__/format-resolvers.test.d.ts.map +1 -0
- package/dist/__tests__/format-resolvers.test.js +577 -0
- package/dist/__tests__/format-resolvers.test.js.map +1 -0
- package/dist/__tests__/imports-extended.test.d.ts +2 -0
- package/dist/__tests__/imports-extended.test.d.ts.map +1 -0
- package/dist/__tests__/imports-extended.test.js +94 -0
- package/dist/__tests__/imports-extended.test.js.map +1 -0
- package/dist/__tests__/lockfile-resolvers.test.d.ts +2 -0
- package/dist/__tests__/lockfile-resolvers.test.d.ts.map +1 -0
- package/dist/__tests__/lockfile-resolvers.test.js +200 -0
- package/dist/__tests__/lockfile-resolvers.test.js.map +1 -0
- package/dist/__tests__/patterns/insertion-at-boundary.test.d.ts +10 -0
- package/dist/__tests__/patterns/insertion-at-boundary.test.d.ts.map +1 -0
- package/dist/__tests__/patterns/insertion-at-boundary.test.js +185 -0
- package/dist/__tests__/patterns/insertion-at-boundary.test.js.map +1 -0
- package/dist/__tests__/patterns/reorder-only.test.d.ts +10 -0
- package/dist/__tests__/patterns/reorder-only.test.d.ts.map +1 -0
- package/dist/__tests__/patterns/reorder-only.test.js +181 -0
- package/dist/__tests__/patterns/reorder-only.test.js.map +1 -0
- package/dist/__tests__/phase-7-2-3b.test.d.ts +6 -0
- package/dist/__tests__/phase-7-2-3b.test.d.ts.map +1 -0
- package/dist/__tests__/phase-7-2-3b.test.js +730 -0
- package/dist/__tests__/phase-7-2-3b.test.js.map +1 -0
- package/dist/__tests__/resolver.test.d.ts +2 -0
- package/dist/__tests__/resolver.test.d.ts.map +1 -0
- package/dist/__tests__/resolver.test.js +927 -0
- package/dist/__tests__/resolver.test.js.map +1 -0
- package/dist/__tests__/resolvers/cargo.test.d.ts +10 -0
- package/dist/__tests__/resolvers/cargo.test.d.ts.map +1 -0
- package/dist/__tests__/resolvers/cargo.test.js +158 -0
- package/dist/__tests__/resolvers/cargo.test.js.map +1 -0
- package/dist/__tests__/resolvers/dockerfile.test.d.ts +8 -0
- package/dist/__tests__/resolvers/dockerfile.test.d.ts.map +1 -0
- package/dist/__tests__/resolvers/dockerfile.test.js +120 -0
- package/dist/__tests__/resolvers/dockerfile.test.js.map +1 -0
- package/dist/__tests__/resolvers/dotenv.test.d.ts +9 -0
- package/dist/__tests__/resolvers/dotenv.test.d.ts.map +1 -0
- package/dist/__tests__/resolvers/dotenv.test.js +113 -0
- package/dist/__tests__/resolvers/dotenv.test.js.map +1 -0
- package/dist/__tests__/resolvers/improvements-v14.test.d.ts +8 -0
- package/dist/__tests__/resolvers/improvements-v14.test.d.ts.map +1 -0
- package/dist/__tests__/resolvers/improvements-v14.test.js +306 -0
- package/dist/__tests__/resolvers/improvements-v14.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +12 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +136 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/classifier.d.ts +21 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +127 -0
- package/dist/classifier.js.map +1 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +200 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +69 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +328 -0
- package/dist/diff.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +39 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +164 -0
- package/dist/parser.js.map +1 -0
- package/dist/patterns/complex.d.ts +5 -0
- package/dist/patterns/complex.d.ts.map +1 -0
- package/dist/patterns/complex.js +27 -0
- package/dist/patterns/complex.js.map +1 -0
- package/dist/patterns/delete-no-change.d.ts +4 -0
- package/dist/patterns/delete-no-change.d.ts.map +1 -0
- package/dist/patterns/delete-no-change.js +75 -0
- package/dist/patterns/delete-no-change.js.map +1 -0
- package/dist/patterns/insertion-at-boundary.d.ts +22 -0
- package/dist/patterns/insertion-at-boundary.d.ts.map +1 -0
- package/dist/patterns/insertion-at-boundary.js +164 -0
- package/dist/patterns/insertion-at-boundary.js.map +1 -0
- package/dist/patterns/non-overlapping.d.ts +4 -0
- package/dist/patterns/non-overlapping.d.ts.map +1 -0
- package/dist/patterns/non-overlapping.js +28 -0
- package/dist/patterns/non-overlapping.js.map +1 -0
- package/dist/patterns/one-side-change.d.ts +4 -0
- package/dist/patterns/one-side-change.d.ts.map +1 -0
- package/dist/patterns/one-side-change.js +45 -0
- package/dist/patterns/one-side-change.js.map +1 -0
- package/dist/patterns/reorder-only.d.ts +14 -0
- package/dist/patterns/reorder-only.d.ts.map +1 -0
- package/dist/patterns/reorder-only.js +81 -0
- package/dist/patterns/reorder-only.js.map +1 -0
- package/dist/patterns/same-change.d.ts +4 -0
- package/dist/patterns/same-change.d.ts.map +1 -0
- package/dist/patterns/same-change.js +25 -0
- package/dist/patterns/same-change.js.map +1 -0
- package/dist/patterns/utils.d.ts +70 -0
- package/dist/patterns/utils.d.ts.map +1 -0
- package/dist/patterns/utils.js +206 -0
- package/dist/patterns/utils.js.map +1 -0
- package/dist/patterns/value-only-change.d.ts +4 -0
- package/dist/patterns/value-only-change.d.ts.map +1 -0
- package/dist/patterns/value-only-change.js +34 -0
- package/dist/patterns/value-only-change.js.map +1 -0
- package/dist/patterns/whitespace-only.d.ts +4 -0
- package/dist/patterns/whitespace-only.d.ts.map +1 -0
- package/dist/patterns/whitespace-only.js +32 -0
- package/dist/patterns/whitespace-only.js.map +1 -0
- package/dist/resolver/assemble.d.ts +25 -0
- package/dist/resolver/assemble.d.ts.map +1 -0
- package/dist/resolver/assemble.js +170 -0
- package/dist/resolver/assemble.js.map +1 -0
- package/dist/resolver/format-dispatch.d.ts +40 -0
- package/dist/resolver/format-dispatch.d.ts.map +1 -0
- package/dist/resolver/format-dispatch.js +51 -0
- package/dist/resolver/format-dispatch.js.map +1 -0
- package/dist/resolver/generated-detection.d.ts +48 -0
- package/dist/resolver/generated-detection.d.ts.map +1 -0
- package/dist/resolver/generated-detection.js +123 -0
- package/dist/resolver/generated-detection.js.map +1 -0
- package/dist/resolver/index.d.ts +26 -0
- package/dist/resolver/index.d.ts.map +1 -0
- package/dist/resolver/index.js +147 -0
- package/dist/resolver/index.js.map +1 -0
- package/dist/resolver/policy.d.ts +53 -0
- package/dist/resolver/policy.d.ts.map +1 -0
- package/dist/resolver/policy.js +99 -0
- package/dist/resolver/policy.js.map +1 -0
- package/dist/resolver/validation.d.ts +28 -0
- package/dist/resolver/validation.d.ts.map +1 -0
- package/dist/resolver/validation.js +96 -0
- package/dist/resolver/validation.js.map +1 -0
- package/dist/resolver.d.ts +18 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +18 -0
- package/dist/resolver.js.map +1 -0
- package/dist/resolvers/cargo.d.ts +34 -0
- package/dist/resolvers/cargo.d.ts.map +1 -0
- package/dist/resolvers/cargo.js +262 -0
- package/dist/resolvers/cargo.js.map +1 -0
- package/dist/resolvers/css.d.ts +60 -0
- package/dist/resolvers/css.d.ts.map +1 -0
- package/dist/resolvers/css.js +531 -0
- package/dist/resolvers/css.js.map +1 -0
- package/dist/resolvers/dispatcher.d.ts +78 -0
- package/dist/resolvers/dispatcher.d.ts.map +1 -0
- package/dist/resolvers/dispatcher.js +290 -0
- package/dist/resolvers/dispatcher.js.map +1 -0
- package/dist/resolvers/dockerfile.d.ts +24 -0
- package/dist/resolvers/dockerfile.d.ts.map +1 -0
- package/dist/resolvers/dockerfile.js +221 -0
- package/dist/resolvers/dockerfile.js.map +1 -0
- package/dist/resolvers/dotenv.d.ts +27 -0
- package/dist/resolvers/dotenv.d.ts.map +1 -0
- package/dist/resolvers/dotenv.js +114 -0
- package/dist/resolvers/dotenv.js.map +1 -0
- package/dist/resolvers/imports.d.ts +63 -0
- package/dist/resolvers/imports.d.ts.map +1 -0
- package/dist/resolvers/imports.js +513 -0
- package/dist/resolvers/imports.js.map +1 -0
- package/dist/resolvers/json.d.ts +48 -0
- package/dist/resolvers/json.d.ts.map +1 -0
- package/dist/resolvers/json.js +363 -0
- package/dist/resolvers/json.js.map +1 -0
- package/dist/resolvers/lockfile-npm.d.ts +38 -0
- package/dist/resolvers/lockfile-npm.d.ts.map +1 -0
- package/dist/resolvers/lockfile-npm.js +267 -0
- package/dist/resolvers/lockfile-npm.js.map +1 -0
- package/dist/resolvers/lockfile-pnpm.d.ts +44 -0
- package/dist/resolvers/lockfile-pnpm.d.ts.map +1 -0
- package/dist/resolvers/lockfile-pnpm.js +277 -0
- package/dist/resolvers/lockfile-pnpm.js.map +1 -0
- package/dist/resolvers/lockfile-yarn.d.ts +40 -0
- package/dist/resolvers/lockfile-yarn.d.ts.map +1 -0
- package/dist/resolvers/lockfile-yarn.js +184 -0
- package/dist/resolvers/lockfile-yarn.js.map +1 -0
- package/dist/resolvers/markdown.d.ts +64 -0
- package/dist/resolvers/markdown.d.ts.map +1 -0
- package/dist/resolvers/markdown.js +335 -0
- package/dist/resolvers/markdown.js.map +1 -0
- package/dist/resolvers/vue.d.ts +65 -0
- package/dist/resolvers/vue.d.ts.map +1 -0
- package/dist/resolvers/vue.js +258 -0
- package/dist/resolvers/vue.js.map +1 -0
- package/dist/resolvers/yaml.d.ts +65 -0
- package/dist/resolvers/yaml.d.ts.map +1 -0
- package/dist/resolvers/yaml.js +405 -0
- package/dist/resolvers/yaml.js.map +1 -0
- package/dist/types.d.ts +256 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests Phase 7.2 — Whitespace normalization + threshold tuning
|
|
3
|
+
* Tests Phase 7.3b — YAML, TS/JS imports, Vue SFC, CSS resolvers
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { resolve } from "../resolver.js";
|
|
7
|
+
import { classifyConflict } from "../parser.js";
|
|
8
|
+
import { tryResolveYamlConflict } from "../resolvers/yaml.js";
|
|
9
|
+
import { tryResolveImportConflict, isImportBlock } from "../resolvers/imports.js";
|
|
10
|
+
import { tryResolveVueConflict, parseSfcBlocks } from "../resolvers/vue.js";
|
|
11
|
+
import { tryResolveCssConflict, parseCssRules } from "../resolvers/css.js";
|
|
12
|
+
import { tryFormatAwareResolve, isYamlFile, isJsFile, isVueFile, isCssFile, } from "../resolvers/dispatcher.js";
|
|
13
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
14
|
+
function makeHunk(base, ours, theirs) {
|
|
15
|
+
return {
|
|
16
|
+
baseLines: base,
|
|
17
|
+
oursLines: ours,
|
|
18
|
+
theirsLines: theirs,
|
|
19
|
+
startLine: 1,
|
|
20
|
+
type: "complex",
|
|
21
|
+
confidence: {
|
|
22
|
+
score: 20, label: "low",
|
|
23
|
+
dimensions: { typeClassification: 20, dataRisk: 0, scopeImpact: 0, fileFrequency: 0, baseAvailability: 0 },
|
|
24
|
+
boosters: [], penalties: [],
|
|
25
|
+
},
|
|
26
|
+
explanation: "test hunk",
|
|
27
|
+
trace: {
|
|
28
|
+
steps: [{ type: "complex", passed: true, reason: "test" }],
|
|
29
|
+
selected: "complex",
|
|
30
|
+
summary: "test",
|
|
31
|
+
hasBase: base.length > 0,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════
|
|
36
|
+
// PHASE 7.2 — Whitespace normalization
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════
|
|
38
|
+
describe("Phase 7.2 — Whitespace normalization améliorée", () => {
|
|
39
|
+
describe("Normalisation des tabs", () => {
|
|
40
|
+
it("détecte whitespace_only quand ours a des tabs et theirs a des espaces", () => {
|
|
41
|
+
// Pour déclencher whitespace_only : ours ≠ base ET theirs ≠ base
|
|
42
|
+
// mais normalize(ours) === normalize(theirs)
|
|
43
|
+
const conflict = {
|
|
44
|
+
oursLines: ["\tconst x = 1;", "\tconst y = 2;"], // tabs
|
|
45
|
+
baseLines: [], // pas de base (diff2)
|
|
46
|
+
theirsLines: [" const x = 1;", " const y = 2;"], // 2 espaces
|
|
47
|
+
startLine: 1,
|
|
48
|
+
endLine: 5,
|
|
49
|
+
};
|
|
50
|
+
const result = classifyConflict(conflict);
|
|
51
|
+
// Sans base, same_change est testé en premier : ours ≠ theirs → non
|
|
52
|
+
// Puis whitespace_only : normalize(tabs) === normalize(2 espaces) → oui
|
|
53
|
+
expect(result.type).toBe("whitespace_only");
|
|
54
|
+
});
|
|
55
|
+
it("détecte whitespace_only entre 2 et 4 espaces (sans base)", () => {
|
|
56
|
+
const conflict = {
|
|
57
|
+
oursLines: [" function foo() {", " return 1;", " }"], // 4 espaces
|
|
58
|
+
baseLines: [], // diff2
|
|
59
|
+
theirsLines: [" function foo() {", " return 1;", " }"], // 2 espaces
|
|
60
|
+
startLine: 1,
|
|
61
|
+
endLine: 8,
|
|
62
|
+
};
|
|
63
|
+
const result = classifyConflict(conflict);
|
|
64
|
+
expect(result.type).toBe("whitespace_only");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("Strip des lignes vides en tête/queue", () => {
|
|
68
|
+
it("détecte whitespace_only quand ours a une ligne vide en tête (diff2)", () => {
|
|
69
|
+
// ours a une ligne vide en tête, theirs n'en a pas
|
|
70
|
+
// normalize(ours) retire la ligne vide → identique à theirs
|
|
71
|
+
const conflict = {
|
|
72
|
+
oursLines: ["", "const x = 1;"],
|
|
73
|
+
baseLines: [],
|
|
74
|
+
theirsLines: ["const x = 1;"],
|
|
75
|
+
startLine: 1,
|
|
76
|
+
endLine: 4,
|
|
77
|
+
};
|
|
78
|
+
const result = classifyConflict(conflict);
|
|
79
|
+
// same_change : ours ≠ theirs → non
|
|
80
|
+
// whitespace_only : normalize(["", "const x = 1;"]) = "const x = 1" === normalize(theirs) → oui
|
|
81
|
+
expect(result.type).toBe("whitespace_only");
|
|
82
|
+
});
|
|
83
|
+
it("détecte whitespace_only quand theirs a des lignes vides en queue (diff2)", () => {
|
|
84
|
+
const conflict = {
|
|
85
|
+
oursLines: ["const a = 1;"],
|
|
86
|
+
baseLines: [],
|
|
87
|
+
theirsLines: ["const a = 1;", "", ""],
|
|
88
|
+
startLine: 1,
|
|
89
|
+
endLine: 5,
|
|
90
|
+
};
|
|
91
|
+
const result = classifyConflict(conflict);
|
|
92
|
+
expect(result.type).toBe("whitespace_only");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("Collapse des espaces internes", () => {
|
|
96
|
+
it("détecte whitespace_only avec espaces multiples (diff2)", () => {
|
|
97
|
+
// ours et theirs diffèrent seulement par le nombre d'espaces
|
|
98
|
+
const conflict = {
|
|
99
|
+
oursLines: ["key: value"],
|
|
100
|
+
baseLines: [],
|
|
101
|
+
theirsLines: ["key: value"],
|
|
102
|
+
startLine: 1,
|
|
103
|
+
endLine: 3,
|
|
104
|
+
};
|
|
105
|
+
const result = classifyConflict(conflict);
|
|
106
|
+
// normalize collapse les espaces multiples
|
|
107
|
+
expect(result.type).toBe("whitespace_only");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe("value_only_change — seuils affinés", () => {
|
|
111
|
+
it("détecte value_only_change sur un hash Git valide (hex lowercase)", () => {
|
|
112
|
+
const conflict = {
|
|
113
|
+
oursLines: ['"integrity": "sha512-abc123def456"'],
|
|
114
|
+
baseLines: [],
|
|
115
|
+
theirsLines: ['"integrity": "sha512-fed789cba321"'],
|
|
116
|
+
startLine: 1,
|
|
117
|
+
endLine: 3,
|
|
118
|
+
};
|
|
119
|
+
// Avec les nouveaux seuils et patterns, les hashes hex purs sont toujours volatils
|
|
120
|
+
const result = classifyConflict(conflict);
|
|
121
|
+
// Peut être value_only_change ou complex selon le ratio — on vérifie juste que ça ne plante pas
|
|
122
|
+
expect(result.type).toBeDefined();
|
|
123
|
+
});
|
|
124
|
+
it("détecte semver comme volatile", () => {
|
|
125
|
+
// La ligne '"version": "1.2.3"' avec le tokenizer original produit le token "1.2.3"
|
|
126
|
+
// (sans les guillemets → split sur `"`) qui matche le pattern semver ^N.N.N$
|
|
127
|
+
const conflict = {
|
|
128
|
+
oursLines: ['"version": "1.2.3"'],
|
|
129
|
+
baseLines: [],
|
|
130
|
+
theirsLines: ['"version": "2.0.0"'],
|
|
131
|
+
startLine: 1,
|
|
132
|
+
endLine: 3,
|
|
133
|
+
};
|
|
134
|
+
const result = classifyConflict(conflict);
|
|
135
|
+
// Les semvers 1.2.3 et 2.0.0 sont des tokens volatils (après split sur `"`)
|
|
136
|
+
expect(result.type).toBe("value_only_change");
|
|
137
|
+
});
|
|
138
|
+
it("ne considère pas un identifiant camelCase standard comme volatile", () => {
|
|
139
|
+
// myFunction et otherFunction ne matchent pas les patterns ^hex$ ni ^UUID$ ni semver etc.
|
|
140
|
+
const conflict = {
|
|
141
|
+
oursLines: ["const handler = myFunction;"],
|
|
142
|
+
baseLines: [],
|
|
143
|
+
theirsLines: ["const handler = otherFunction;"],
|
|
144
|
+
startLine: 1,
|
|
145
|
+
endLine: 3,
|
|
146
|
+
};
|
|
147
|
+
const result = classifyConflict(conflict);
|
|
148
|
+
// Ces noms ne sont pas volatils → complex
|
|
149
|
+
expect(result.type).toBe("complex");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ═══════════════════════════════════════════════════════════════
|
|
154
|
+
// PHASE 7.3b — Résolveur YAML
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════
|
|
156
|
+
describe("Phase 7.3b — Résolveur YAML", () => {
|
|
157
|
+
describe("tryResolveYamlConflict — cas de base", () => {
|
|
158
|
+
it("fusionne deux clés ajoutées indépendamment", () => {
|
|
159
|
+
const base = ["name: app", "version: 1.0.0"];
|
|
160
|
+
const ours = ["name: app", "version: 1.0.0", "author: Alice"];
|
|
161
|
+
const theirs = ["name: app", "version: 1.0.0", "license: MIT"];
|
|
162
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
163
|
+
expect(result.mergedLines).not.toBeNull();
|
|
164
|
+
const merged = result.mergedLines.join("\n");
|
|
165
|
+
expect(merged).toContain("author: Alice");
|
|
166
|
+
expect(merged).toContain("license: MIT");
|
|
167
|
+
expect(result.resolvedKeys).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
it("résout seul ours a modifié une clé", () => {
|
|
170
|
+
const base = ["name: app", "port: 3000"];
|
|
171
|
+
const ours = ["name: app", "port: 4000"]; // ours a changé port
|
|
172
|
+
const theirs = ["name: app", "port: 3000"]; // theirs n'a pas changé
|
|
173
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
174
|
+
expect(result.mergedLines).not.toBeNull();
|
|
175
|
+
const merged = result.mergedLines.join("\n");
|
|
176
|
+
expect(merged).toContain("port: 4000");
|
|
177
|
+
});
|
|
178
|
+
it("résout seul theirs a modifié une clé", () => {
|
|
179
|
+
const base = ["name: app", "debug: false"];
|
|
180
|
+
const ours = ["name: app", "debug: false"];
|
|
181
|
+
const theirs = ["name: app", "debug: true"];
|
|
182
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
183
|
+
expect(result.mergedLines).not.toBeNull();
|
|
184
|
+
const merged = result.mergedLines.join("\n");
|
|
185
|
+
expect(merged).toContain("debug: true");
|
|
186
|
+
});
|
|
187
|
+
it("retourne null si les deux côtés ont modifié la même clé différemment", () => {
|
|
188
|
+
const base = ["version: 1.0.0"];
|
|
189
|
+
const ours = ["version: 2.0.0"];
|
|
190
|
+
const theirs = ["version: 3.0.0"];
|
|
191
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
192
|
+
expect(result.mergedLines).toBeNull();
|
|
193
|
+
expect(result.unresolvedKeys).toBeGreaterThan(0);
|
|
194
|
+
});
|
|
195
|
+
it("résout le cas same_change", () => {
|
|
196
|
+
const base = ["name: app"];
|
|
197
|
+
const ours = ["name: app", "type: module"];
|
|
198
|
+
const theirs = ["name: app", "type: module"];
|
|
199
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
200
|
+
expect(result.mergedLines).not.toBeNull();
|
|
201
|
+
expect(result.mergedLines.join("\n")).toContain("type: module");
|
|
202
|
+
});
|
|
203
|
+
it("rejette les blocs avec anchors YAML", () => {
|
|
204
|
+
const base = ["defaults: &defaults", " port: 3000"];
|
|
205
|
+
const ours = ["defaults: &defaults", " port: 4000"];
|
|
206
|
+
const theirs = ["defaults: &defaults", " port: 3000"];
|
|
207
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
208
|
+
// Anchors non supportés → null
|
|
209
|
+
expect(result.mergedLines).toBeNull();
|
|
210
|
+
expect(result.reason).toMatch(/anchor/i);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("tryResolveYamlConflict — blocs imbriqués", () => {
|
|
214
|
+
it("fusionne des sous-clés ajoutées indépendamment", () => {
|
|
215
|
+
const base = [
|
|
216
|
+
"scripts:",
|
|
217
|
+
" build: tsc",
|
|
218
|
+
" test: jest",
|
|
219
|
+
];
|
|
220
|
+
const ours = [
|
|
221
|
+
"scripts:",
|
|
222
|
+
" build: tsc",
|
|
223
|
+
" test: jest",
|
|
224
|
+
" lint: eslint .",
|
|
225
|
+
];
|
|
226
|
+
const theirs = [
|
|
227
|
+
"scripts:",
|
|
228
|
+
" build: tsc",
|
|
229
|
+
" test: jest",
|
|
230
|
+
" format: prettier .",
|
|
231
|
+
];
|
|
232
|
+
const result = tryResolveYamlConflict(base, ours, theirs);
|
|
233
|
+
expect(result.mergedLines).not.toBeNull();
|
|
234
|
+
const merged = result.mergedLines.join("\n");
|
|
235
|
+
expect(merged).toContain("lint: eslint .");
|
|
236
|
+
expect(merged).toContain("format: prettier .");
|
|
237
|
+
expect(merged).toContain("build: tsc");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe("Intégration YAML via resolve()", () => {
|
|
241
|
+
it("résout un conflit YAML via le moteur principal", () => {
|
|
242
|
+
const yamlConflict = `name: my-app
|
|
243
|
+
version: 1.0.0
|
|
244
|
+
<<<<<<< ours
|
|
245
|
+
author: Alice
|
|
246
|
+
port: 3000
|
|
247
|
+
||||||| base
|
|
248
|
+
port: 3000
|
|
249
|
+
=======
|
|
250
|
+
license: MIT
|
|
251
|
+
port: 3000
|
|
252
|
+
>>>>>>> theirs`;
|
|
253
|
+
const result = resolve(yamlConflict, "config.yaml");
|
|
254
|
+
expect(result.stats.autoResolved).toBe(1);
|
|
255
|
+
expect(result.mergedContent).not.toBeNull();
|
|
256
|
+
const merged = result.mergedContent;
|
|
257
|
+
expect(merged).toContain("author: Alice");
|
|
258
|
+
expect(merged).toContain("license: MIT");
|
|
259
|
+
});
|
|
260
|
+
it("la raison mentionne le résolveur yaml", () => {
|
|
261
|
+
const yamlConflict = `name: app
|
|
262
|
+
<<<<<<< ours
|
|
263
|
+
debug: true
|
|
264
|
+
||||||| base
|
|
265
|
+
debug: false
|
|
266
|
+
=======
|
|
267
|
+
debug: false
|
|
268
|
+
>>>>>>> theirs`;
|
|
269
|
+
const result = resolve(yamlConflict, "config.yml");
|
|
270
|
+
if (result.stats.autoResolved > 0) {
|
|
271
|
+
expect(result.resolutions[0].resolutionReason).toMatch(/\[yaml\]/i);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
// ═══════════════════════════════════════════════════════════════
|
|
277
|
+
// PHASE 7.3b — Résolveur d'imports TS/JS
|
|
278
|
+
// ═══════════════════════════════════════════════════════════════
|
|
279
|
+
describe("Phase 7.3b — Résolveur d'imports TS/JS", () => {
|
|
280
|
+
describe("isImportBlock", () => {
|
|
281
|
+
it("détecte un bloc entièrement composé d'imports", () => {
|
|
282
|
+
const lines = [
|
|
283
|
+
"import React from 'react';",
|
|
284
|
+
"import { useState } from 'react';",
|
|
285
|
+
"import type { FC } from 'react';",
|
|
286
|
+
];
|
|
287
|
+
expect(isImportBlock(lines)).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
it("rejette un bloc avec du code non-import", () => {
|
|
290
|
+
const lines = [
|
|
291
|
+
"import React from 'react';",
|
|
292
|
+
"const x = 1;",
|
|
293
|
+
];
|
|
294
|
+
expect(isImportBlock(lines)).toBe(false);
|
|
295
|
+
});
|
|
296
|
+
it("accepte les lignes vides et commentaires dans un bloc d'imports", () => {
|
|
297
|
+
const lines = [
|
|
298
|
+
"// React",
|
|
299
|
+
"import React from 'react';",
|
|
300
|
+
"",
|
|
301
|
+
"import { useState } from 'react';",
|
|
302
|
+
];
|
|
303
|
+
expect(isImportBlock(lines)).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
describe("tryResolveImportConflict — cas de base", () => {
|
|
307
|
+
it("fusionne deux imports ajoutés indépendamment", () => {
|
|
308
|
+
const base = ["import React from 'react';"];
|
|
309
|
+
const ours = ["import React from 'react';", "import { useState } from 'react';"];
|
|
310
|
+
const theirs = ["import React from 'react';", "import { useEffect } from 'react';"];
|
|
311
|
+
const result = tryResolveImportConflict(base, ours, theirs);
|
|
312
|
+
expect(result.mergedLines).not.toBeNull();
|
|
313
|
+
const merged = result.mergedLines.join("\n");
|
|
314
|
+
expect(merged).toContain("useState");
|
|
315
|
+
expect(merged).toContain("useEffect");
|
|
316
|
+
});
|
|
317
|
+
it("fusionne des named imports du même module", () => {
|
|
318
|
+
const base = ["import { useState } from 'react';"];
|
|
319
|
+
const ours = ["import { useState, useEffect } from 'react';"];
|
|
320
|
+
const theirs = ["import { useState, useCallback } from 'react';"];
|
|
321
|
+
const result = tryResolveImportConflict(base, ours, theirs);
|
|
322
|
+
expect(result.mergedLines).not.toBeNull();
|
|
323
|
+
const merged = result.mergedLines.join("\n");
|
|
324
|
+
expect(merged).toContain("useState");
|
|
325
|
+
expect(merged).toContain("useEffect");
|
|
326
|
+
expect(merged).toContain("useCallback");
|
|
327
|
+
});
|
|
328
|
+
it("déduplique les imports identiques ajoutés des deux côtés", () => {
|
|
329
|
+
const base = [];
|
|
330
|
+
const ours = ["import React from 'react';"];
|
|
331
|
+
const theirs = ["import React from 'react';"];
|
|
332
|
+
const result = tryResolveImportConflict(base, ours, theirs);
|
|
333
|
+
expect(result.mergedLines).not.toBeNull();
|
|
334
|
+
// Un seul import React dans le résultat
|
|
335
|
+
const count = result.mergedLines.filter((l) => l.includes("import React")).length;
|
|
336
|
+
expect(count).toBe(1);
|
|
337
|
+
});
|
|
338
|
+
it("retourne null si les deux côtés ont modifié le même named import de façon conflictuelle", () => {
|
|
339
|
+
// ours a supprimé 'a' de l'import, theirs a supprimé 'b' → conflit (chacun a supprimé quelque chose)
|
|
340
|
+
const base = ["import { a, b } from 'lib';"];
|
|
341
|
+
const ours = ["import { a } from 'lib';"]; // supprimé b
|
|
342
|
+
const theirs = ["import { b } from 'lib';"]; // supprimé a
|
|
343
|
+
const result = tryResolveImportConflict(base, ours, theirs);
|
|
344
|
+
// ours supprime b que theirs garde, theirs supprime a que ours garde → conflit non résolvable
|
|
345
|
+
expect(result.mergedLines).toBeNull();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
describe("Intégration imports via resolve()", () => {
|
|
349
|
+
it("résout un conflit d'imports TypeScript via le moteur principal", () => {
|
|
350
|
+
const tsConflict = `<<<<<<< ours
|
|
351
|
+
import React from 'react';
|
|
352
|
+
import { useState } from 'react';
|
|
353
|
+
import { MyComponent } from './components';
|
|
354
|
+
||||||| base
|
|
355
|
+
import React from 'react';
|
|
356
|
+
import { useState } from 'react';
|
|
357
|
+
=======
|
|
358
|
+
import React from 'react';
|
|
359
|
+
import { useState } from 'react';
|
|
360
|
+
import { useEffect } from 'react';
|
|
361
|
+
>>>>>>> theirs`;
|
|
362
|
+
const result = resolve(tsConflict, "src/app.tsx");
|
|
363
|
+
expect(result.stats.autoResolved).toBe(1);
|
|
364
|
+
expect(result.mergedContent).not.toBeNull();
|
|
365
|
+
const merged = result.mergedContent;
|
|
366
|
+
expect(merged).toContain("MyComponent");
|
|
367
|
+
expect(merged).toContain("useEffect");
|
|
368
|
+
});
|
|
369
|
+
it("la raison mentionne le résolveur imports", () => {
|
|
370
|
+
const tsConflict = `<<<<<<< ours
|
|
371
|
+
import { A } from './a';
|
|
372
|
+
import { B } from './b';
|
|
373
|
+
||||||| base
|
|
374
|
+
import { A } from './a';
|
|
375
|
+
=======
|
|
376
|
+
import { A } from './a';
|
|
377
|
+
import { C } from './c';
|
|
378
|
+
>>>>>>> theirs`;
|
|
379
|
+
const result = resolve(tsConflict, "src/index.ts");
|
|
380
|
+
if (result.stats.autoResolved > 0) {
|
|
381
|
+
expect(result.resolutions[0].resolutionReason).toMatch(/\[imports\]/i);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
// ═══════════════════════════════════════════════════════════════
|
|
387
|
+
// PHASE 7.3b — Résolveur Vue SFC
|
|
388
|
+
// ═══════════════════════════════════════════════════════════════
|
|
389
|
+
describe("Phase 7.3b — Résolveur Vue SFC", () => {
|
|
390
|
+
describe("parseSfcBlocks", () => {
|
|
391
|
+
it("découpe un fichier Vue en blocs", () => {
|
|
392
|
+
const lines = [
|
|
393
|
+
"<template>",
|
|
394
|
+
" <div>Hello</div>",
|
|
395
|
+
"</template>",
|
|
396
|
+
"",
|
|
397
|
+
"<script setup>",
|
|
398
|
+
"const msg = 'world';",
|
|
399
|
+
"</script>",
|
|
400
|
+
"",
|
|
401
|
+
"<style scoped>",
|
|
402
|
+
".container { color: red; }",
|
|
403
|
+
"</style>",
|
|
404
|
+
];
|
|
405
|
+
const blocks = parseSfcBlocks(lines);
|
|
406
|
+
const names = blocks.map((b) => b.name);
|
|
407
|
+
expect(names).toContain("template");
|
|
408
|
+
expect(names).toContain("script");
|
|
409
|
+
expect(names).toContain("style");
|
|
410
|
+
});
|
|
411
|
+
it("gère un fichier avec seulement un template", () => {
|
|
412
|
+
const lines = ["<template>", " <p>test</p>", "</template>"];
|
|
413
|
+
const blocks = parseSfcBlocks(lines);
|
|
414
|
+
expect(blocks[0].name).toBe("template");
|
|
415
|
+
expect(blocks[0].lines).toContain(" <p>test</p>");
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
describe("tryResolveVueConflict — blocs indépendants", () => {
|
|
419
|
+
const baseVue = [
|
|
420
|
+
"<template>",
|
|
421
|
+
" <div>Base</div>",
|
|
422
|
+
"</template>",
|
|
423
|
+
"",
|
|
424
|
+
"<script setup>",
|
|
425
|
+
"const x = 1;",
|
|
426
|
+
"</script>",
|
|
427
|
+
];
|
|
428
|
+
it("accepte la modification de script quand seul ours a changé", () => {
|
|
429
|
+
const ours = [
|
|
430
|
+
"<template>",
|
|
431
|
+
" <div>Base</div>",
|
|
432
|
+
"</template>",
|
|
433
|
+
"",
|
|
434
|
+
"<script setup>",
|
|
435
|
+
"const x = 2; // modifié par ours",
|
|
436
|
+
"</script>",
|
|
437
|
+
];
|
|
438
|
+
const theirs = [...baseVue]; // identique à base
|
|
439
|
+
const result = tryResolveVueConflict(baseVue, ours, theirs);
|
|
440
|
+
expect(result.mergedLines).not.toBeNull();
|
|
441
|
+
const merged = result.mergedLines.join("\n");
|
|
442
|
+
expect(merged).toContain("modifié par ours");
|
|
443
|
+
});
|
|
444
|
+
it("accepte la modification de template quand seul theirs a changé", () => {
|
|
445
|
+
const ours = [...baseVue]; // identique à base
|
|
446
|
+
const theirs = [
|
|
447
|
+
"<template>",
|
|
448
|
+
" <div>Modifié par theirs</div>",
|
|
449
|
+
"</template>",
|
|
450
|
+
"",
|
|
451
|
+
"<script setup>",
|
|
452
|
+
"const x = 1;",
|
|
453
|
+
"</script>",
|
|
454
|
+
];
|
|
455
|
+
const result = tryResolveVueConflict(baseVue, ours, theirs);
|
|
456
|
+
expect(result.mergedLines).not.toBeNull();
|
|
457
|
+
const merged = result.mergedLines.join("\n");
|
|
458
|
+
expect(merged).toContain("Modifié par theirs");
|
|
459
|
+
});
|
|
460
|
+
it("retourne null si les deux côtés ont modifié le même bloc", () => {
|
|
461
|
+
const ours = [
|
|
462
|
+
"<template>",
|
|
463
|
+
" <div>Version ours</div>",
|
|
464
|
+
"</template>",
|
|
465
|
+
"",
|
|
466
|
+
"<script setup>",
|
|
467
|
+
"const x = 1;",
|
|
468
|
+
"</script>",
|
|
469
|
+
];
|
|
470
|
+
const theirs = [
|
|
471
|
+
"<template>",
|
|
472
|
+
" <div>Version theirs</div>",
|
|
473
|
+
"</template>",
|
|
474
|
+
"",
|
|
475
|
+
"<script setup>",
|
|
476
|
+
"const x = 1;",
|
|
477
|
+
"</script>",
|
|
478
|
+
];
|
|
479
|
+
const result = tryResolveVueConflict(baseVue, ours, theirs);
|
|
480
|
+
expect(result.mergedLines).toBeNull();
|
|
481
|
+
expect(result.conflictedBlocks.length).toBeGreaterThan(0);
|
|
482
|
+
});
|
|
483
|
+
it("intègre un nouveau bloc style ajouté par theirs", () => {
|
|
484
|
+
const ours = [...baseVue]; // pas de style
|
|
485
|
+
const theirs = [
|
|
486
|
+
...baseVue,
|
|
487
|
+
"",
|
|
488
|
+
"<style scoped>",
|
|
489
|
+
".container { color: blue; }",
|
|
490
|
+
"</style>",
|
|
491
|
+
];
|
|
492
|
+
const result = tryResolveVueConflict(baseVue, ours, theirs);
|
|
493
|
+
expect(result.mergedLines).not.toBeNull();
|
|
494
|
+
const merged = result.mergedLines.join("\n");
|
|
495
|
+
expect(merged).toContain("color: blue");
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
describe("Intégration Vue via resolve()", () => {
|
|
499
|
+
it("résout un conflit Vue SFC avec blocs indépendants", () => {
|
|
500
|
+
const vueConflict = `<template>
|
|
501
|
+
<div>Hello</div>
|
|
502
|
+
</template>
|
|
503
|
+
|
|
504
|
+
<<<<<<< ours
|
|
505
|
+
<script setup>
|
|
506
|
+
import { ref } from 'vue';
|
|
507
|
+
const count = ref(0);
|
|
508
|
+
</script>
|
|
509
|
+
||||||| base
|
|
510
|
+
<script setup>
|
|
511
|
+
const msg = 'hello';
|
|
512
|
+
</script>
|
|
513
|
+
=======
|
|
514
|
+
<script setup>
|
|
515
|
+
const msg = 'world';
|
|
516
|
+
</script>
|
|
517
|
+
>>>>>>> theirs
|
|
518
|
+
|
|
519
|
+
<style>
|
|
520
|
+
.app { color: red; }
|
|
521
|
+
</style>`;
|
|
522
|
+
const result = resolve(vueConflict, "App.vue");
|
|
523
|
+
// Si les deux ont modifié le script → non résolvable (correct)
|
|
524
|
+
// ou si c'est un one_side_change selon la base
|
|
525
|
+
expect(result.stats).toBeDefined();
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
// ═══════════════════════════════════════════════════════════════
|
|
530
|
+
// PHASE 7.3b — Résolveur CSS
|
|
531
|
+
// ═══════════════════════════════════════════════════════════════
|
|
532
|
+
describe("Phase 7.3b — Résolveur CSS", () => {
|
|
533
|
+
describe("parseCssRules", () => {
|
|
534
|
+
it("parse des règles CSS basiques", () => {
|
|
535
|
+
const lines = [
|
|
536
|
+
".button {",
|
|
537
|
+
" color: red;",
|
|
538
|
+
" font-size: 14px;",
|
|
539
|
+
"}",
|
|
540
|
+
"",
|
|
541
|
+
".container {",
|
|
542
|
+
" max-width: 1200px;",
|
|
543
|
+
"}",
|
|
544
|
+
];
|
|
545
|
+
const rules = parseCssRules(lines);
|
|
546
|
+
const ruleNames = rules.filter((r) => r.kind === "rule").map((r) => r.selector);
|
|
547
|
+
expect(ruleNames).toContain(".button");
|
|
548
|
+
expect(ruleNames).toContain(".container");
|
|
549
|
+
});
|
|
550
|
+
it("détecte les at-rules", () => {
|
|
551
|
+
const lines = [
|
|
552
|
+
"@media (max-width: 768px) {",
|
|
553
|
+
" .container { width: 100%; }",
|
|
554
|
+
"}",
|
|
555
|
+
];
|
|
556
|
+
const rules = parseCssRules(lines);
|
|
557
|
+
const atRules = rules.filter((r) => r.kind === "at-rule");
|
|
558
|
+
expect(atRules.length).toBeGreaterThan(0);
|
|
559
|
+
expect(atRules[0].selector).toContain("@media");
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
describe("tryResolveCssConflict — cas de base", () => {
|
|
563
|
+
it("fusionne deux sélecteurs ajoutés indépendamment", () => {
|
|
564
|
+
const base = [".container {", " max-width: 1200px;", "}"];
|
|
565
|
+
const ours = [
|
|
566
|
+
".container {",
|
|
567
|
+
" max-width: 1200px;",
|
|
568
|
+
"}",
|
|
569
|
+
".button {",
|
|
570
|
+
" color: blue;",
|
|
571
|
+
"}",
|
|
572
|
+
];
|
|
573
|
+
const theirs = [
|
|
574
|
+
".container {",
|
|
575
|
+
" max-width: 1200px;",
|
|
576
|
+
"}",
|
|
577
|
+
".header {",
|
|
578
|
+
" font-size: 24px;",
|
|
579
|
+
"}",
|
|
580
|
+
];
|
|
581
|
+
const result = tryResolveCssConflict(base, ours, theirs);
|
|
582
|
+
expect(result.mergedLines).not.toBeNull();
|
|
583
|
+
const merged = result.mergedLines.join("\n");
|
|
584
|
+
expect(merged).toContain(".button");
|
|
585
|
+
expect(merged).toContain(".header");
|
|
586
|
+
});
|
|
587
|
+
it("fusionne des propriétés ajoutées dans le même sélecteur", () => {
|
|
588
|
+
const base = [".btn {", " color: blue;", "}"];
|
|
589
|
+
const ours = [".btn {", " color: blue;", " font-size: 14px;", "}"];
|
|
590
|
+
const theirs = [".btn {", " color: blue;", " padding: 8px;", "}"];
|
|
591
|
+
const result = tryResolveCssConflict(base, ours, theirs);
|
|
592
|
+
expect(result.mergedLines).not.toBeNull();
|
|
593
|
+
const merged = result.mergedLines.join("\n");
|
|
594
|
+
expect(merged).toContain("font-size: 14px");
|
|
595
|
+
expect(merged).toContain("padding: 8px");
|
|
596
|
+
});
|
|
597
|
+
it("retourne null si les deux côtés modifient la même propriété différemment", () => {
|
|
598
|
+
const base = [".btn {", " color: blue;", "}"];
|
|
599
|
+
const ours = [".btn {", " color: red;", "}"];
|
|
600
|
+
const theirs = [".btn {", " color: green;", "}"];
|
|
601
|
+
const result = tryResolveCssConflict(base, ours, theirs);
|
|
602
|
+
expect(result.mergedLines).toBeNull();
|
|
603
|
+
});
|
|
604
|
+
it("accepte un sélecteur inchangé", () => {
|
|
605
|
+
const base = [".container {", " width: 100%;", "}"];
|
|
606
|
+
const ours = [".container {", " width: 100%;", "}", ".new {", " color: red;", "}"];
|
|
607
|
+
const theirs = [...base]; // theirs n'a pas changé
|
|
608
|
+
const result = tryResolveCssConflict(base, ours, theirs);
|
|
609
|
+
expect(result.mergedLines).not.toBeNull();
|
|
610
|
+
const merged = result.mergedLines.join("\n");
|
|
611
|
+
expect(merged).toContain(".new");
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
describe("Intégration CSS via resolve()", () => {
|
|
615
|
+
it("résout un conflit CSS avec sélecteurs indépendants", () => {
|
|
616
|
+
const cssConflict = `.container {
|
|
617
|
+
max-width: 1200px;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
<<<<<<< ours
|
|
621
|
+
.button {
|
|
622
|
+
color: blue;
|
|
623
|
+
padding: 8px;
|
|
624
|
+
}
|
|
625
|
+
||||||| base
|
|
626
|
+
=======
|
|
627
|
+
.header {
|
|
628
|
+
font-size: 24px;
|
|
629
|
+
}
|
|
630
|
+
>>>>>>> theirs`;
|
|
631
|
+
const result = resolve(cssConflict, "styles.css");
|
|
632
|
+
expect(result.stats.autoResolved).toBe(1);
|
|
633
|
+
expect(result.mergedContent).not.toBeNull();
|
|
634
|
+
const merged = result.mergedContent;
|
|
635
|
+
expect(merged).toContain(".button");
|
|
636
|
+
expect(merged).toContain(".header");
|
|
637
|
+
});
|
|
638
|
+
it("la raison mentionne le résolveur css", () => {
|
|
639
|
+
const cssConflict = `<<<<<<< ours
|
|
640
|
+
.a { color: red; }
|
|
641
|
+
||||||| base
|
|
642
|
+
=======
|
|
643
|
+
.b { color: blue; }
|
|
644
|
+
>>>>>>> theirs`;
|
|
645
|
+
const result = resolve(cssConflict, "app.scss");
|
|
646
|
+
if (result.stats.autoResolved > 0) {
|
|
647
|
+
expect(result.resolutions[0].resolutionReason).toMatch(/\[css\]/i);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
// ═══════════════════════════════════════════════════════════════
|
|
653
|
+
// PHASE 7.3b — Dispatcher — détection des nouveaux types
|
|
654
|
+
// ═══════════════════════════════════════════════════════════════
|
|
655
|
+
describe("Phase 7.3b — Dispatcher — nouveaux types de fichiers", () => {
|
|
656
|
+
describe("isYamlFile", () => {
|
|
657
|
+
it("détecte .yaml et .yml", () => {
|
|
658
|
+
expect(isYamlFile("config.yaml")).toBe(true);
|
|
659
|
+
expect(isYamlFile("config.yml")).toBe(true);
|
|
660
|
+
expect(isYamlFile(".github/workflows/ci.yml")).toBe(true);
|
|
661
|
+
});
|
|
662
|
+
it("ne détecte pas les non-YAML", () => {
|
|
663
|
+
expect(isYamlFile("config.json")).toBe(false);
|
|
664
|
+
expect(isYamlFile("app.ts")).toBe(false);
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
describe("isJsFile", () => {
|
|
668
|
+
it("détecte .ts, .js, .tsx, .jsx, .mjs, .cjs", () => {
|
|
669
|
+
expect(isJsFile("app.ts")).toBe(true);
|
|
670
|
+
expect(isJsFile("App.tsx")).toBe(true);
|
|
671
|
+
expect(isJsFile("index.js")).toBe(true);
|
|
672
|
+
expect(isJsFile("Component.jsx")).toBe(true);
|
|
673
|
+
expect(isJsFile("server.mjs")).toBe(true);
|
|
674
|
+
expect(isJsFile("require.cjs")).toBe(true);
|
|
675
|
+
});
|
|
676
|
+
it("ne détecte pas les non-JS", () => {
|
|
677
|
+
expect(isJsFile("styles.css")).toBe(false);
|
|
678
|
+
expect(isJsFile("App.vue")).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
describe("isVueFile", () => {
|
|
682
|
+
it("détecte .vue", () => {
|
|
683
|
+
expect(isVueFile("App.vue")).toBe(true);
|
|
684
|
+
expect(isVueFile("components/Button.vue")).toBe(true);
|
|
685
|
+
});
|
|
686
|
+
it("ne détecte pas les non-Vue", () => {
|
|
687
|
+
expect(isVueFile("app.ts")).toBe(false);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
describe("isCssFile", () => {
|
|
691
|
+
it("détecte .css, .scss, .less, .sass", () => {
|
|
692
|
+
expect(isCssFile("styles.css")).toBe(true);
|
|
693
|
+
expect(isCssFile("theme.scss")).toBe(true);
|
|
694
|
+
expect(isCssFile("vars.less")).toBe(true);
|
|
695
|
+
expect(isCssFile("mixins.sass")).toBe(true);
|
|
696
|
+
});
|
|
697
|
+
it("ne détecte pas les non-CSS", () => {
|
|
698
|
+
expect(isCssFile("app.ts")).toBe(false);
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
describe("tryFormatAwareResolve — nouveaux résolveurs", () => {
|
|
702
|
+
it("utilise le résolveur yaml pour .yaml", () => {
|
|
703
|
+
const hunk = makeHunk(["name: app", "port: 3000"], ["name: app", "port: 4000"], ["name: app", "port: 3000"]);
|
|
704
|
+
const result = tryFormatAwareResolve(hunk, "config.yaml");
|
|
705
|
+
expect(result.resolverUsed).toBe("yaml");
|
|
706
|
+
});
|
|
707
|
+
it("utilise le résolveur vue pour .vue", () => {
|
|
708
|
+
const hunk = makeHunk(["<template>", " <div>Base</div>", "</template>"], ["<template>", " <div>Ours</div>", "</template>"], ["<template>", " <div>Base</div>", "</template>"]);
|
|
709
|
+
const result = tryFormatAwareResolve(hunk, "App.vue");
|
|
710
|
+
expect(result.resolverUsed).toBe("vue");
|
|
711
|
+
});
|
|
712
|
+
it("utilise le résolveur css pour .scss", () => {
|
|
713
|
+
const hunk = makeHunk([".btn { color: blue; }"], [".btn { color: blue; }", ".new { font-size: 14px; }"], [".btn { color: blue; }"]);
|
|
714
|
+
const result = tryFormatAwareResolve(hunk, "styles.scss");
|
|
715
|
+
expect(result.resolverUsed).toBe("css");
|
|
716
|
+
});
|
|
717
|
+
it("utilise le résolveur imports pour .ts avec bloc d'imports", () => {
|
|
718
|
+
const hunk = makeHunk(["import React from 'react';"], ["import React from 'react';", "import { useState } from 'react';"], ["import React from 'react';", "import { useEffect } from 'react';"]);
|
|
719
|
+
const result = tryFormatAwareResolve(hunk, "app.ts");
|
|
720
|
+
expect(result.resolverUsed).toBe("imports");
|
|
721
|
+
});
|
|
722
|
+
it("n'utilise pas le résolveur imports pour .ts avec code non-import", () => {
|
|
723
|
+
const hunk = makeHunk(["const x = 1;"], ["const x = 2;"], ["const x = 3;"]);
|
|
724
|
+
const result = tryFormatAwareResolve(hunk, "app.ts");
|
|
725
|
+
// Pas de résolveur spécialisé pour du code TS général
|
|
726
|
+
expect(result.resolverUsed).toBe("none");
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
//# sourceMappingURL=phase-7-2-3b.test.js.map
|