Skip to content

Commit 2a77746

Browse files
committed
Implements diffing fixture tests. Fixes microsoft#165041
1 parent f93ad8a commit 2a77746

File tree

118 files changed

+5290
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+5290
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Diffing Fixture Tests
2+
3+
Every folder in `fixtures` represents a test.
4+
The file that starts with `1.` is diffed against the file that starts with `2.`. Use `tsx` instead of `ts` to avoid compiler/linter errors for typescript diff files.
5+
6+
* When you delete `*.actual.diff.json`, they are regenerated.
7+
* Make sure to delete `*.expected.diff.json`, as they make the tests fail.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import assert = require('assert');
7+
import { readdirSync, readFileSync, existsSync, writeFileSync } from 'fs';
8+
import { join } from 'path';
9+
import { URI } from 'vs/base/common/uri';
10+
import { SmartLinesDiffComputer } from 'vs/editor/common/diff/smartLinesDiffComputer';
11+
import { StandardLinesDiffComputer } from 'vs/editor/common/diff/standardLinesDiffComputer';
12+
13+
suite('diff fixtures', () => {
14+
15+
const fixturesDir = join(URI.parse(__dirname.replace('out/vs/editor', 'src/vs/editor')).fsPath, 'fixtures');
16+
const folders = readdirSync(fixturesDir);
17+
18+
for (const folder of folders) {
19+
for (const diffingAlgoName of ['smart', 'experimental']) {
20+
21+
test(`${folder}-${diffingAlgoName}`, () => {
22+
const folderPath = join(fixturesDir, folder);
23+
const files = readdirSync(folderPath);
24+
25+
const firstFileName = files.find(f => f.startsWith('1.'))!;
26+
const secondFileName = files.find(f => f.startsWith('2.'))!;
27+
28+
const firstContentLines = readFileSync(join(folderPath, firstFileName), 'utf8').split(/\r\n|\r|\n/);
29+
const secondContentLines = readFileSync(join(folderPath, secondFileName), 'utf8').split(/\r\n|\r|\n/);
30+
31+
const diffingAlgo = diffingAlgoName === 'smart' ? new SmartLinesDiffComputer() : new StandardLinesDiffComputer();
32+
33+
const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace: false, maxComputationTime: Number.MAX_SAFE_INTEGER });
34+
35+
const diffingResult: DiffingResult = {
36+
originalFileName: `./${firstFileName}`,
37+
modifiedFileName: `./${secondFileName}`,
38+
diffs: diff.changes.map<IDetailedDiff>(c => ({
39+
originalRange: c.originalRange.toString(),
40+
modifiedRange: c.modifiedRange.toString(),
41+
innerChanges: c.innerChanges?.map<IDiff>(c => ({
42+
originalRange: c.originalRange.toString(),
43+
modifiedRange: c.modifiedRange.toString(),
44+
})) || null
45+
}))
46+
};
47+
48+
const actualFilePath = join(folderPath, `${diffingAlgoName}.actual.diff.json`);
49+
const expectedFilePath = join(folderPath, `${diffingAlgoName}.expected.diff.json`);
50+
51+
const expectedFileContent = JSON.stringify(diffingResult, null, '\t');
52+
53+
if (!existsSync(actualFilePath)) {
54+
writeFileSync(actualFilePath, expectedFileContent);
55+
writeFileSync(expectedFilePath, expectedFileContent);
56+
throw new Error('No actual file! Actual and expected files were written.');
57+
} else {
58+
const actualFileContent = readFileSync(actualFilePath, 'utf8');
59+
const actualFileDiffResult: DiffingResult = JSON.parse(actualFileContent);
60+
61+
try {
62+
assert.deepStrictEqual(actualFileDiffResult, diffingResult);
63+
} catch (e) {
64+
writeFileSync(expectedFilePath, expectedFileContent);
65+
throw e;
66+
}
67+
}
68+
69+
if (existsSync(expectedFilePath)) {
70+
throw new Error('Expected file exists! Please delete it.');
71+
}
72+
});
73+
}
74+
}
75+
});
76+
77+
interface DiffingResult {
78+
originalFileName: string;
79+
modifiedFileName: string;
80+
81+
diffs: IDetailedDiff[];
82+
}
83+
84+
interface IDetailedDiff {
85+
originalRange: string; // [startLineNumber, endLineNumberExclusive)
86+
modifiedRange: string; // [startLineNumber, endLineNumberExclusive)
87+
innerChanges: IDiff[] | null;
88+
}
89+
90+
interface IDiff {
91+
originalRange: string; // [1,18 -> 1,19]
92+
modifiedRange: string; // [1,18 -> 1,19]
93+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { CompareResult } from 'vs/base/common/arrays';
7+
import { autorun, derived } from 'vs/base/common/observable';
8+
import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model';
9+
import { localize } from 'vs/nls';
10+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
11+
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
12+
import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils';
13+
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
14+
import { CodeEditorView } from './codeEditorView';
15+
16+
export class ResultCodeEditorView extends CodeEditorView {
17+
private readonly decorations = derived('result.decorations', reader => {
18+
const viewModel = this.viewModel.read(reader);
19+
if (!viewModel) {
20+
return [];
21+
}
22+
const model = viewModel.model;
23+
const result = new Array<IModelDeltaDecoration>();
24+
25+
const baseRangeWithStoreAndTouchingDiffs = join(
26+
model.modifiedBaseRanges.read(reader),
27+
model.resultDiffs.read(reader),
28+
(baseRange, diff) => baseRange.baseRange.touches(diff.inputRange)
29+
? CompareResult.neitherLessOrGreaterThan
30+
: LineRange.compareByStart(
31+
baseRange.baseRange,
32+
diff.inputRange
33+
)
34+
);
35+
36+
const activeModifiedBaseRange = viewModel.activeModifiedBaseRange.read(reader);
37+
38+
for (const m of baseRangeWithStoreAndTouchingDiffs) {
39+
const modifiedBaseRange = m.left;
40+
41+
if (modifiedBaseRange) {
42+
const range = model.getRangeInResult(modifiedBaseRange.baseRange, reader).toInclusiveRange();
43+
if (range) {
44+
const blockClassNames = ['merge-editor-block'];
45+
const isHandled = model.isHandled(modifiedBaseRange).read(reader);
46+
if (isHandled) {
47+
blockClassNames.push('handled');
48+
}
49+
if (modifiedBaseRange === activeModifiedBaseRange) {
50+
blockClassNames.push('focused');
51+
}
52+
blockClassNames.push('result');
53+
54+
result.push({
55+
range,
56+
options: {
57+
isWholeLine: true,
58+
blockClassName: blockClassNames.join(' '),
59+
description: 'Result Diff',
60+
minimap: {
61+
position: MinimapPosition.Gutter,
62+
color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor },
63+
},
64+
overviewRuler: {
65+
position: OverviewRulerLane.Center,
66+
color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor },
67+
}
68+
}
69+
});
70+
}
71+
}
72+
73+
for (const diff of m.rights) {
74+
const range = diff.outputRange.toInclusiveRange();
75+
if (range) {
76+
result.push({
77+
range,
78+
options: {
79+
className: `merge-editor-diff result`,
80+
description: 'Merge Editor',
81+
isWholeLine: true,
82+
}
83+
});
84+
}
85+
86+
if (diff.rangeMappings) {
87+
for (const d of diff.rangeMappings) {
88+
result.push({
89+
range: d.outputRange,
90+
options: {
91+
className: `merge-editor-diff-word result`,
92+
description: 'Merge Editor'
93+
}
94+
});
95+
}
96+
}
97+
}
98+
}
99+
return result;
100+
});
101+
102+
constructor(
103+
@IInstantiationService instantiationService: IInstantiationService
104+
) {
105+
super(instantiationService);
106+
107+
this._register(applyObservableDecorations(this.editor, this.decorations));
108+
109+
110+
this._register(autorun('update remainingConflicts label', reader => {
111+
const model = this.model.read(reader);
112+
if (!model) {
113+
return;
114+
}
115+
const count = model.unhandledConflictsCount.read(reader);
116+
117+
this.htmlElements.detail.innerText = count === 1
118+
? localize(
119+
'mergeEditor.remainingConflicts',
120+
'{0} Conflict Remaining',
121+
count
122+
)
123+
: localize(
124+
'mergeEditor.remainingConflict',
125+
'{0} Conflicts Remaining ',
126+
count
127+
);
128+
129+
}));
130+
}
131+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { CompareResult } from 'vs/base/common/arrays';
7+
import { autorun, derived } from 'vs/base/common/observable';
8+
import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model';
9+
import { localize } from 'vs/nls';
10+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
11+
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
12+
import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils';
13+
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
14+
import { CodeEditorView } from './codeEditorView';
15+
16+
export class ResultCodeEditorView extends CodeEditorView {
17+
private readonly decorations = derived('result.decorations', reader => {
18+
const viewModel = this.viewModel.read(reader);
19+
if (!viewModel) {
20+
return [];
21+
}
22+
const model = viewModel.model;
23+
const result = new Array<IModelDeltaDecoration>();
24+
25+
const baseRangeWithStoreAndTouchingDiffs = join(
26+
model.modifiedBaseRanges.read(reader),
27+
model.resultDiffs.read(reader),
28+
(baseRange, diff) => baseRange.baseRange.touches(diff.inputRange)
29+
? CompareResult.neitherLessOrGreaterThan
30+
: LineRange.compareByStart(
31+
baseRange.baseRange,
32+
diff.inputRange
33+
)
34+
);
35+
36+
const activeModifiedBaseRange = viewModel.activeModifiedBaseRange.read(reader);
37+
38+
for (const m of baseRangeWithStoreAndTouchingDiffs) {
39+
const modifiedBaseRange = m.left;
40+
41+
if (modifiedBaseRange) {
42+
const range = model.getRangeInResult(modifiedBaseRange.baseRange, reader).toInclusiveRange();
43+
if (range) {
44+
const blockClassNames = ['merge-editor-block'];
45+
const isHandled = model.isHandled(modifiedBaseRange).read(reader);
46+
if (isHandled) {
47+
blockClassNames.push('handled');
48+
}
49+
if (modifiedBaseRange === activeModifiedBaseRange) {
50+
blockClassNames.push('focused');
51+
}
52+
blockClassNames.push('result');
53+
54+
result.push({
55+
range,
56+
options: {
57+
isWholeLine: true,
58+
blockClassName: blockClassNames.join(' '),
59+
description: 'Result Diff',
60+
minimap: {
61+
position: MinimapPosition.Gutter,
62+
color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor },
63+
},
64+
overviewRuler: {
65+
position: OverviewRulerLane.Center,
66+
color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor },
67+
}
68+
}
69+
});
70+
}
71+
}
72+
73+
74+
if (!modifiedBaseRange || modifiedBaseRange.isConflicting) {
75+
for (const diff of m.rights) {
76+
const range = diff.outputRange.toInclusiveRange();
77+
if (range) {
78+
result.push({
79+
range,
80+
options: {
81+
className: `merge-editor-diff result`,
82+
description: 'Merge Editor',
83+
isWholeLine: true,
84+
}
85+
});
86+
}
87+
88+
if (diff.rangeMappings) {
89+
for (const d of diff.rangeMappings) {
90+
result.push({
91+
range: d.outputRange,
92+
options: {
93+
className: `merge-editor-diff-word result`,
94+
description: 'Merge Editor'
95+
}
96+
});
97+
}
98+
}
99+
}
100+
}
101+
}
102+
return result;
103+
});
104+
105+
constructor(
106+
@IInstantiationService instantiationService: IInstantiationService
107+
) {
108+
super(instantiationService);
109+
110+
this._register(applyObservableDecorations(this.editor, this.decorations));
111+
112+
113+
this._register(autorun('update remainingConflicts label', reader => {
114+
const model = this.model.read(reader);
115+
if (!model) {
116+
return;
117+
}
118+
const count = model.unhandledConflictsCount.read(reader);
119+
120+
this.htmlElements.detail.innerText = count === 1
121+
? localize(
122+
'mergeEditor.remainingConflicts',
123+
'{0} Conflict Remaining',
124+
count
125+
)
126+
: localize(
127+
'mergeEditor.remainingConflict',
128+
'{0} Conflicts Remaining ',
129+
count
130+
);
131+
132+
}));
133+
}
134+
}

0 commit comments

Comments
 (0)