@theia/scm 1.77.0-next.47 → 1.77.0-next.56
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/lib/browser/decorations/scm-navigator-decorator.js +1 -1
- package/lib/browser/decorations/scm-navigator-decorator.js.map +1 -1
- package/lib/browser/dirty-diff/content-lines.spec.js +2 -1
- package/lib/browser/dirty-diff/content-lines.spec.js.map +1 -1
- package/lib/browser/dirty-diff/diff-computer.js +2 -1
- package/lib/browser/dirty-diff/diff-computer.js.map +1 -1
- package/lib/browser/dirty-diff/diff-computer.spec.js +2 -1
- package/lib/browser/dirty-diff/diff-computer.spec.js.map +1 -1
- package/lib/browser/dirty-diff/dirty-diff-navigator.js.map +1 -1
- package/lib/browser/dirty-diff/dirty-diff-widget.js +1 -1
- package/lib/browser/dirty-diff/dirty-diff-widget.js.map +1 -1
- package/lib/browser/merge-editor/merge-editor-contribution.d.ts +1 -1
- package/lib/browser/merge-editor/merge-editor-contribution.d.ts.map +1 -1
- package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts +1 -1
- package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts.map +1 -1
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js +1 -1
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js.map +1 -1
- package/lib/browser/merge-editor/view/merge-editor-view-zones.js +1 -1
- package/lib/browser/merge-editor/view/merge-editor-view-zones.js.map +1 -1
- package/lib/browser/multi-diff-editor/multi-diff-editor-module.d.ts +4 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-module.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-module.js +34 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-module.js.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-uri.d.ts +15 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-uri.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-uri.js +68 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-uri.js.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget-factory.d.ts +18 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget-factory.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget-factory.js +76 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget-factory.js.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget.spec.d.ts +2 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget.spec.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget.spec.js +835 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor-widget.spec.js.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.d.ts +163 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.js +563 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.js.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.spec.d.ts +2 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.spec.d.ts.map +1 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.spec.js +167 -0
- package/lib/browser/multi-diff-editor/multi-diff-editor.spec.js.map +1 -0
- package/lib/browser/scm-action-button-widget.js +1 -1
- package/lib/browser/scm-action-button-widget.js.map +1 -1
- package/lib/browser/scm-amend-component.js +2 -1
- package/lib/browser/scm-amend-component.js.map +1 -1
- package/lib/browser/scm-amend-widget.js +1 -1
- package/lib/browser/scm-amend-widget.js.map +1 -1
- package/lib/browser/scm-commit-widget.js +2 -2
- package/lib/browser/scm-commit-widget.js.map +1 -1
- package/lib/browser/scm-contribution.js +1 -1
- package/lib/browser/scm-contribution.js.map +1 -1
- package/lib/browser/scm-frontend-module.d.ts.map +1 -1
- package/lib/browser/scm-frontend-module.js +2 -0
- package/lib/browser/scm-frontend-module.js.map +1 -1
- package/lib/browser/scm-history-graph-widget.js +2 -2
- package/lib/browser/scm-history-graph-widget.js.map +1 -1
- package/lib/browser/scm-history-graph-widget.spec.js +2 -1
- package/lib/browser/scm-history-graph-widget.spec.js.map +1 -1
- package/lib/browser/scm-input.js +3 -2
- package/lib/browser/scm-input.js.map +1 -1
- package/lib/browser/scm-provider.d.ts +2 -0
- package/lib/browser/scm-provider.d.ts.map +1 -1
- package/lib/browser/scm-quick-open-service.js +1 -1
- package/lib/browser/scm-quick-open-service.js.map +1 -1
- package/lib/browser/scm-repositories-widget.js +2 -2
- package/lib/browser/scm-repositories-widget.js.map +1 -1
- package/lib/browser/scm-repositories-widget.spec.js +2 -1
- package/lib/browser/scm-repositories-widget.spec.js.map +1 -1
- package/lib/browser/scm-service.js +1 -1
- package/lib/browser/scm-service.js.map +1 -1
- package/lib/browser/scm-tree-label-provider.js +1 -1
- package/lib/browser/scm-tree-label-provider.js.map +1 -1
- package/lib/browser/scm-tree-model.js +1 -1
- package/lib/browser/scm-tree-model.js.map +1 -1
- package/lib/browser/scm-tree-widget.js +2 -2
- package/lib/browser/scm-tree-widget.js.map +1 -1
- package/package.json +6 -6
- package/src/browser/dirty-diff/dirty-diff-navigator.ts +1 -1
- package/src/browser/multi-diff-editor/multi-diff-editor-module.ts +35 -0
- package/src/browser/multi-diff-editor/multi-diff-editor-uri.ts +83 -0
- package/src/browser/multi-diff-editor/multi-diff-editor-widget-factory.ts +81 -0
- package/src/browser/multi-diff-editor/multi-diff-editor-widget.spec.ts +878 -0
- package/src/browser/multi-diff-editor/multi-diff-editor.spec.ts +189 -0
- package/src/browser/multi-diff-editor/multi-diff-editor.ts +628 -0
- package/src/browser/scm-frontend-module.ts +3 -0
- package/src/browser/scm-input.ts +1 -1
- package/src/browser/scm-provider.ts +2 -0
- package/src/browser/style/multi-diff-editor.css +113 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// *****************************************************************************
|
|
3
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
4
|
+
//
|
|
5
|
+
// This program and the accompanying materials are made available under the
|
|
6
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
7
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
8
|
+
//
|
|
9
|
+
// This Source Code may also be made available under the following Secondary
|
|
10
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
11
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
12
|
+
// with the GNU Classpath Exception which is available at
|
|
13
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
14
|
+
//
|
|
15
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
|
+
// *****************************************************************************
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const tslib_1 = require("tslib");
|
|
19
|
+
const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
|
|
20
|
+
let disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
21
|
+
const frontend_application_config_provider_1 = require("@theia/core/lib/browser/frontend-application-config-provider");
|
|
22
|
+
frontend_application_config_provider_1.FrontendApplicationConfigProvider.set({});
|
|
23
|
+
const chai_1 = require("chai");
|
|
24
|
+
const sinon = tslib_1.__importStar(require("sinon"));
|
|
25
|
+
const monaco_editor_1 = require("@theia/monaco/lib/browser/monaco-editor");
|
|
26
|
+
const monaco_diff_editor_1 = require("@theia/monaco/lib/browser/monaco-diff-editor");
|
|
27
|
+
const multi_diff_editor_widget_factory_1 = require("./multi-diff-editor-widget-factory");
|
|
28
|
+
const core_1 = require("@theia/core");
|
|
29
|
+
const browser_1 = require("@theia/core/lib/browser");
|
|
30
|
+
const inversify_1 = require("@theia/core/shared/inversify");
|
|
31
|
+
const multi_diff_editor_1 = require("./multi-diff-editor");
|
|
32
|
+
const multi_diff_editor_uri_1 = require("./multi-diff-editor-uri");
|
|
33
|
+
disableJSDOM();
|
|
34
|
+
describe('Multi-Diff Editor — widgets', () => {
|
|
35
|
+
before(() => {
|
|
36
|
+
disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
37
|
+
});
|
|
38
|
+
after(() => {
|
|
39
|
+
disableJSDOM();
|
|
40
|
+
});
|
|
41
|
+
// Minimal LabelProvider mock — only the methods DiffEntryHeaderWidget uses.
|
|
42
|
+
const mockLabelProvider = {
|
|
43
|
+
getIcon: (_) => 'codicon codicon-file',
|
|
44
|
+
getName: (uri) => uri.path.base,
|
|
45
|
+
getLongName: (uri) => uri.path.toString()
|
|
46
|
+
};
|
|
47
|
+
const sampleResource = {
|
|
48
|
+
originalUri: new core_1.URI('file:///workspace/a.ts'),
|
|
49
|
+
modifiedUri: new core_1.URI('file:///workspace/b.ts')
|
|
50
|
+
};
|
|
51
|
+
describe('DiffEntryHeaderWidget', () => {
|
|
52
|
+
it('should render icon, name and description', () => {
|
|
53
|
+
const widget = new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider);
|
|
54
|
+
try {
|
|
55
|
+
(0, chai_1.expect)(widget.node.querySelector('.multi-diff-entry-icon')).to.exist;
|
|
56
|
+
(0, chai_1.expect)(widget.node.querySelector('.multi-diff-entry-label')?.textContent).to.equal('b.ts');
|
|
57
|
+
(0, chai_1.expect)(widget.node.querySelector('.multi-diff-entry-description')?.textContent).to.equal('/workspace/b.ts');
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
widget.dispose();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
it('should expose aria-expanded=true by default', () => {
|
|
64
|
+
const widget = new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider);
|
|
65
|
+
try {
|
|
66
|
+
(0, chai_1.expect)(widget.node.getAttribute('aria-expanded')).to.equal('true');
|
|
67
|
+
(0, chai_1.expect)(widget.node.querySelector('.codicon-chevron-down')).to.exist;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
widget.dispose();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it('should toggle chevron and aria-expanded on setCollapsed', () => {
|
|
74
|
+
const widget = new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider);
|
|
75
|
+
try {
|
|
76
|
+
widget.setCollapsed(true);
|
|
77
|
+
(0, chai_1.expect)(widget.node.getAttribute('aria-expanded')).to.equal('false');
|
|
78
|
+
(0, chai_1.expect)(widget.node.querySelector('.codicon-chevron-right')).to.exist;
|
|
79
|
+
(0, chai_1.expect)(widget.node.querySelector('.codicon-chevron-down')).to.be.null;
|
|
80
|
+
widget.setCollapsed(false);
|
|
81
|
+
(0, chai_1.expect)(widget.node.getAttribute('aria-expanded')).to.equal('true');
|
|
82
|
+
(0, chai_1.expect)(widget.node.querySelector('.codicon-chevron-down')).to.exist;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
widget.dispose();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
it('should fire onDidToggleCollapse when clicked', () => {
|
|
89
|
+
const widget = new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider);
|
|
90
|
+
try {
|
|
91
|
+
let fired = 0;
|
|
92
|
+
widget.onDidToggleCollapse(() => fired++);
|
|
93
|
+
widget.node.click();
|
|
94
|
+
(0, chai_1.expect)(fired).to.equal(1);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
widget.dispose();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('DiffEntryWidget', () => {
|
|
102
|
+
function createEntry() {
|
|
103
|
+
const header = new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider);
|
|
104
|
+
const entry = new multi_diff_editor_1.DiffEntryWidget(sampleResource, header);
|
|
105
|
+
return { entry, header };
|
|
106
|
+
}
|
|
107
|
+
it('should start in loading state with MIN_ENTRY_HEIGHT', () => {
|
|
108
|
+
const { entry } = createEntry();
|
|
109
|
+
try {
|
|
110
|
+
(0, chai_1.expect)(entry.editorWidget).to.be.undefined;
|
|
111
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.false;
|
|
112
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.MIN_ENTRY_HEIGHT}px`);
|
|
113
|
+
(0, chai_1.expect)(entry.node.querySelector('.multi-diff-entry-loading')).to.exist;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
entry.dispose();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
it('should transition to error state on setError', () => {
|
|
120
|
+
const { entry } = createEntry();
|
|
121
|
+
try {
|
|
122
|
+
entry.setError('boom');
|
|
123
|
+
(0, chai_1.expect)(entry.node.querySelector('.multi-diff-entry-loading')).to.be.null;
|
|
124
|
+
(0, chai_1.expect)(entry.node.querySelector('.multi-diff-entry-error')?.textContent).to.contain('boom');
|
|
125
|
+
(0, chai_1.expect)(entry.editorWidget).to.be.undefined;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
entry.dispose();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
it('should fit short content and cap large entries before the viewport size is known', () => {
|
|
132
|
+
const { entry } = createEntry();
|
|
133
|
+
try {
|
|
134
|
+
entry.setHeight(50);
|
|
135
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('50px');
|
|
136
|
+
entry.setHeight(10_000);
|
|
137
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
138
|
+
entry.setHeight(multi_diff_editor_1.MIN_ENTRY_HEIGHT + 42);
|
|
139
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.MIN_ENTRY_HEIGHT + 42}px`);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
entry.dispose();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
it('should shrink to HEADER_HEIGHT when collapsed and restore height when expanded', () => {
|
|
146
|
+
const { entry } = createEntry();
|
|
147
|
+
try {
|
|
148
|
+
entry.setHeight(multi_diff_editor_1.MIN_ENTRY_HEIGHT + 100);
|
|
149
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.MIN_ENTRY_HEIGHT + 100}px`);
|
|
150
|
+
entry.setCollapsed(true);
|
|
151
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.true;
|
|
152
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
153
|
+
entry.setCollapsed(false);
|
|
154
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.false;
|
|
155
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.MIN_ENTRY_HEIGHT + 100}px`);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
entry.dispose();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
it('should remember requested height while collapsed and apply it on expand', () => {
|
|
162
|
+
const { entry } = createEntry();
|
|
163
|
+
try {
|
|
164
|
+
entry.setCollapsed(true);
|
|
165
|
+
// setHeight while collapsed updates the remembered expanded height only.
|
|
166
|
+
entry.setHeight(multi_diff_editor_1.MIN_ENTRY_HEIGHT + 50);
|
|
167
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
168
|
+
entry.setCollapsed(false);
|
|
169
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.MIN_ENTRY_HEIGHT + 50}px`);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
entry.dispose();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
it('should hide content when collapsed, including content loaded afterwards', () => {
|
|
176
|
+
const { entry } = createEntry();
|
|
177
|
+
try {
|
|
178
|
+
entry.setCollapsed(true);
|
|
179
|
+
(0, chai_1.expect)(entry['currentContent'].isHidden).to.be.true;
|
|
180
|
+
entry.setError('boom');
|
|
181
|
+
(0, chai_1.expect)(entry['currentContent'].isHidden).to.be.true;
|
|
182
|
+
entry.setCollapsed(false);
|
|
183
|
+
(0, chai_1.expect)(entry['currentContent'].isHidden).to.be.false;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
entry.dispose();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
it('should retain the available height across content updates and collapse', () => {
|
|
190
|
+
const { entry } = createEntry();
|
|
191
|
+
try {
|
|
192
|
+
entry.setAvailableHeight(800);
|
|
193
|
+
entry.setHeight(200);
|
|
194
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('800px');
|
|
195
|
+
entry.setCollapsed(true);
|
|
196
|
+
entry.setAvailableHeight(600);
|
|
197
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
198
|
+
entry.setCollapsed(false);
|
|
199
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('600px');
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
entry.dispose();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it('should be a no-op when setCollapsed is called with the current value', () => {
|
|
206
|
+
const { entry } = createEntry();
|
|
207
|
+
try {
|
|
208
|
+
let fired = 0;
|
|
209
|
+
entry.onDidChangeCollapsed(() => fired++);
|
|
210
|
+
entry.setCollapsed(false);
|
|
211
|
+
(0, chai_1.expect)(fired).to.equal(0);
|
|
212
|
+
entry.setCollapsed(true);
|
|
213
|
+
(0, chai_1.expect)(fired).to.equal(1);
|
|
214
|
+
entry.setCollapsed(true);
|
|
215
|
+
(0, chai_1.expect)(fired).to.equal(1);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
entry.dispose();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
it('should toggle collapsed when the header fires onDidToggleCollapse', () => {
|
|
222
|
+
const { entry, header } = createEntry();
|
|
223
|
+
try {
|
|
224
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.false;
|
|
225
|
+
header.node.click();
|
|
226
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.true;
|
|
227
|
+
header.node.click();
|
|
228
|
+
(0, chai_1.expect)(entry.isCollapsed).to.be.false;
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
entry.dispose();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
describe('entry loading', () => {
|
|
236
|
+
function createEntry(load) {
|
|
237
|
+
return new multi_diff_editor_1.DiffEntryWidget(sampleResource, new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider), load);
|
|
238
|
+
}
|
|
239
|
+
it('should skip collapsed entries, load on expansion, and suppress concurrent or repeated loads', async () => {
|
|
240
|
+
let resolve;
|
|
241
|
+
const load = sinon.stub().returns(new Promise(r => resolve = r));
|
|
242
|
+
const entry = createEntry(load);
|
|
243
|
+
const widget = new browser_1.Widget();
|
|
244
|
+
try {
|
|
245
|
+
entry.setCollapsed(true);
|
|
246
|
+
await entry.loadEditor();
|
|
247
|
+
(0, chai_1.expect)(load.callCount).to.equal(0);
|
|
248
|
+
entry.setCollapsed(false);
|
|
249
|
+
await entry.loadEditor();
|
|
250
|
+
(0, chai_1.expect)(load.callCount).to.equal(1);
|
|
251
|
+
entry.setCollapsed(true);
|
|
252
|
+
resolve(widget);
|
|
253
|
+
await Promise.resolve();
|
|
254
|
+
(0, chai_1.expect)(entry.editorWidget).to.equal(widget);
|
|
255
|
+
(0, chai_1.expect)(widget.isHidden).to.be.true;
|
|
256
|
+
entry.setCollapsed(false);
|
|
257
|
+
await entry.loadEditor();
|
|
258
|
+
(0, chai_1.expect)(load.callCount).to.equal(1);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
entry.dispose();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
it('should offer a native Retry button, restore loading state, and serialize retries', async () => {
|
|
265
|
+
let resolve;
|
|
266
|
+
const load = sinon.stub();
|
|
267
|
+
load.onFirstCall().rejects(new Error('provider unavailable'));
|
|
268
|
+
load.onSecondCall().returns(new Promise(r => resolve = r));
|
|
269
|
+
const entry = createEntry(load);
|
|
270
|
+
const widget = new browser_1.Widget();
|
|
271
|
+
try {
|
|
272
|
+
await entry.loadEditor();
|
|
273
|
+
const button = entry.node.querySelector('button');
|
|
274
|
+
(0, chai_1.expect)(button.textContent).to.equal('Retry');
|
|
275
|
+
(0, chai_1.expect)(button.type).to.equal('button');
|
|
276
|
+
await entry.loadEditor();
|
|
277
|
+
(0, chai_1.expect)(load.callCount).to.equal(1);
|
|
278
|
+
button.click();
|
|
279
|
+
(0, chai_1.expect)(entry.node.querySelector('.multi-diff-entry-loading')).to.exist;
|
|
280
|
+
button.click();
|
|
281
|
+
await entry.loadEditor(true);
|
|
282
|
+
(0, chai_1.expect)(load.callCount).to.equal(2);
|
|
283
|
+
resolve(widget);
|
|
284
|
+
await Promise.resolve();
|
|
285
|
+
(0, chai_1.expect)(entry.editorWidget).to.equal(widget);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
entry.dispose();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
it('should dispose editors completing after disposal without emitting a load event', async () => {
|
|
292
|
+
let resolve;
|
|
293
|
+
const entry = createEntry(() => new Promise(r => resolve = r));
|
|
294
|
+
const loaded = sinon.spy();
|
|
295
|
+
entry.onDidLoadEditor(loaded);
|
|
296
|
+
const pending = entry.loadEditor();
|
|
297
|
+
entry.dispose();
|
|
298
|
+
const widget = new browser_1.Widget();
|
|
299
|
+
resolve(widget);
|
|
300
|
+
await pending;
|
|
301
|
+
(0, chai_1.expect)(widget.isDisposed).to.be.true;
|
|
302
|
+
(0, chai_1.expect)(loaded.called).to.be.false;
|
|
303
|
+
});
|
|
304
|
+
it('should ignore rejected loads after disposal', async () => {
|
|
305
|
+
let reject;
|
|
306
|
+
const entry = createEntry(() => new Promise((_resolve, r) => reject = r));
|
|
307
|
+
const setError = sinon.spy(entry, 'setError');
|
|
308
|
+
const pending = entry.loadEditor();
|
|
309
|
+
entry.dispose();
|
|
310
|
+
reject(new Error('late failure'));
|
|
311
|
+
await pending;
|
|
312
|
+
(0, chai_1.expect)(setError.called).to.be.false;
|
|
313
|
+
});
|
|
314
|
+
it('should track both diff content heights as unchanged regions are hidden or expanded and dispose listeners', () => {
|
|
315
|
+
const originalChanged = new core_1.Emitter();
|
|
316
|
+
const modifiedChanged = new core_1.Emitter();
|
|
317
|
+
let originalHeight = 300;
|
|
318
|
+
let modifiedHeight = 400;
|
|
319
|
+
const monacoEditor = Object.create(monaco_diff_editor_1.MonacoDiffEditor.prototype);
|
|
320
|
+
Object.defineProperty(monacoEditor, 'diffEditor', {
|
|
321
|
+
value: {
|
|
322
|
+
updateOptions: sinon.spy(),
|
|
323
|
+
getOriginalEditor: () => ({ getContentHeight: () => originalHeight, onDidContentSizeChange: originalChanged.event }),
|
|
324
|
+
getModifiedEditor: () => ({ getContentHeight: () => modifiedHeight, onDidContentSizeChange: modifiedChanged.event })
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const widget = new browser_1.Widget();
|
|
328
|
+
Object.defineProperty(widget, 'editor', { value: monacoEditor });
|
|
329
|
+
const entry = createEntry(async () => widget);
|
|
330
|
+
try {
|
|
331
|
+
entry.setEditor(widget);
|
|
332
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${400 + multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
333
|
+
originalHeight = 140;
|
|
334
|
+
modifiedHeight = 150;
|
|
335
|
+
modifiedChanged.fire();
|
|
336
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${150 + multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
337
|
+
entry.setCollapsed(true);
|
|
338
|
+
originalHeight = 350;
|
|
339
|
+
originalChanged.fire();
|
|
340
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
341
|
+
entry.setCollapsed(false);
|
|
342
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${350 + multi_diff_editor_1.HEADER_HEIGHT}px`);
|
|
343
|
+
entry.dispose();
|
|
344
|
+
const setHeight = sinon.spy(entry, 'setHeight');
|
|
345
|
+
originalChanged.fire();
|
|
346
|
+
modifiedChanged.fire();
|
|
347
|
+
(0, chai_1.expect)(setHeight.called).to.be.false;
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
entry.dispose();
|
|
351
|
+
originalChanged.dispose();
|
|
352
|
+
modifiedChanged.dispose();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
describe('content-fit sizing and inner scrolling', () => {
|
|
357
|
+
for (const isDiff of [false, true]) {
|
|
358
|
+
it(`should release vertical wheel events at ${isDiff ? 'diff' : 'code'} boundaries without preventing outer scrolling`, () => {
|
|
359
|
+
const changed = new core_1.Emitter();
|
|
360
|
+
let scrollTop = 0;
|
|
361
|
+
let contentHeight = 6000;
|
|
362
|
+
const control = {
|
|
363
|
+
getContentHeight: () => contentHeight,
|
|
364
|
+
onDidContentSizeChange: changed.event,
|
|
365
|
+
updateOptions: sinon.spy(),
|
|
366
|
+
getScrollTop: () => scrollTop,
|
|
367
|
+
getScrollHeight: () => 6000,
|
|
368
|
+
getLayoutInfo: () => ({ height: 1000 })
|
|
369
|
+
};
|
|
370
|
+
const monacoEditor = Object.create(isDiff ? monaco_diff_editor_1.MonacoDiffEditor.prototype : monaco_editor_1.MonacoEditor.prototype);
|
|
371
|
+
if (isDiff) {
|
|
372
|
+
Object.defineProperty(monacoEditor, 'diffEditor', {
|
|
373
|
+
value: { getOriginalEditor: () => control, getModifiedEditor: () => control, updateOptions: control.updateOptions }
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
monacoEditor.getControl = () => control;
|
|
378
|
+
}
|
|
379
|
+
const widget = new browser_1.Widget();
|
|
380
|
+
Object.defineProperty(widget, 'editor', { value: monacoEditor });
|
|
381
|
+
const entry = new multi_diff_editor_1.DiffEntryWidget(sampleResource, new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider));
|
|
382
|
+
const targets = Array.from({ length: isDiff ? 2 : 1 }, () => {
|
|
383
|
+
const node = document.createElement('div');
|
|
384
|
+
widget.node.appendChild(node);
|
|
385
|
+
node.addEventListener('wheel', event => event.preventDefault());
|
|
386
|
+
return node;
|
|
387
|
+
});
|
|
388
|
+
const wheel = (target, deltaY, options = {}) => {
|
|
389
|
+
const event = new window.WheelEvent('wheel', { deltaY, bubbles: true, cancelable: true, ...options });
|
|
390
|
+
target.dispatchEvent(event);
|
|
391
|
+
return event.defaultPrevented;
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
entry.setEditor(widget);
|
|
395
|
+
for (const target of targets) {
|
|
396
|
+
scrollTop = 0;
|
|
397
|
+
(0, chai_1.expect)(wheel(target, -100)).to.be.false;
|
|
398
|
+
(0, chai_1.expect)(wheel(target, 100)).to.be.true;
|
|
399
|
+
scrollTop = 2500;
|
|
400
|
+
(0, chai_1.expect)(wheel(target, -100)).to.be.true;
|
|
401
|
+
(0, chai_1.expect)(wheel(target, 100)).to.be.true;
|
|
402
|
+
scrollTop = 5000;
|
|
403
|
+
(0, chai_1.expect)(wheel(target, 100)).to.be.false;
|
|
404
|
+
(0, chai_1.expect)(wheel(target, -100)).to.be.true;
|
|
405
|
+
(0, chai_1.expect)(wheel(target, 100, { deltaX: 200 })).to.be.true;
|
|
406
|
+
(0, chai_1.expect)(wheel(target, 100, { ctrlKey: true })).to.be.true;
|
|
407
|
+
(0, chai_1.expect)(wheel(target, 100, { shiftKey: true })).to.be.true;
|
|
408
|
+
}
|
|
409
|
+
contentHeight = 20;
|
|
410
|
+
changed.fire();
|
|
411
|
+
(0, chai_1.expect)(wheel(targets[0], 100)).to.be.true;
|
|
412
|
+
entry.dispose();
|
|
413
|
+
(0, chai_1.expect)(wheel(targets[0], 100)).to.be.true;
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
entry.dispose();
|
|
417
|
+
changed.dispose();
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
it(`should size ${isDiff ? 'diff' : 'code'} content including borders and toggle scrolling on content changes and resizes`, () => {
|
|
421
|
+
const changed = new core_1.Emitter();
|
|
422
|
+
const originalChanged = new core_1.Emitter();
|
|
423
|
+
let contentHeight = 20;
|
|
424
|
+
let originalHeight = 10;
|
|
425
|
+
const updateOptions = sinon.spy((_options) => changed.fire());
|
|
426
|
+
const control = {
|
|
427
|
+
getContentHeight: () => contentHeight,
|
|
428
|
+
onDidContentSizeChange: changed.event,
|
|
429
|
+
updateOptions
|
|
430
|
+
};
|
|
431
|
+
const monacoEditor = Object.create(isDiff ? monaco_diff_editor_1.MonacoDiffEditor.prototype : monaco_editor_1.MonacoEditor.prototype);
|
|
432
|
+
if (isDiff) {
|
|
433
|
+
Object.defineProperty(monacoEditor, 'diffEditor', {
|
|
434
|
+
value: {
|
|
435
|
+
getOriginalEditor: () => ({ getContentHeight: () => originalHeight, onDidContentSizeChange: originalChanged.event }),
|
|
436
|
+
getModifiedEditor: () => control,
|
|
437
|
+
updateOptions
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
monacoEditor.getControl = () => control;
|
|
443
|
+
}
|
|
444
|
+
const widget = new browser_1.Widget();
|
|
445
|
+
Object.defineProperty(widget, 'editor', { value: monacoEditor });
|
|
446
|
+
const entry = new multi_diff_editor_1.DiffEntryWidget(sampleResource, new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider));
|
|
447
|
+
entry.node.style.border = '1px solid black';
|
|
448
|
+
browser_1.Widget.attach(entry, document.body);
|
|
449
|
+
const chrome = multi_diff_editor_1.HEADER_HEIGHT + 2;
|
|
450
|
+
const expectScrolling = (enabled) => (0, chai_1.expect)(updateOptions.lastCall.args[0]).to.deep.equal({
|
|
451
|
+
scrollbar: { vertical: enabled ? 'auto' : 'hidden', handleMouseWheel: enabled, alwaysConsumeMouseWheel: false }
|
|
452
|
+
});
|
|
453
|
+
try {
|
|
454
|
+
entry.setAvailableHeight(250, false);
|
|
455
|
+
entry.setEditor(widget);
|
|
456
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${20 + chrome}px`);
|
|
457
|
+
expectScrolling(false);
|
|
458
|
+
(0, chai_1.expect)(updateOptions.callCount).to.equal(1);
|
|
459
|
+
changed.fire();
|
|
460
|
+
(0, chai_1.expect)(updateOptions.callCount).to.equal(1);
|
|
461
|
+
contentHeight = 6000;
|
|
462
|
+
changed.fire();
|
|
463
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
464
|
+
expectScrolling(true);
|
|
465
|
+
(0, chai_1.expect)(updateOptions.callCount).to.equal(2);
|
|
466
|
+
entry.setAvailableHeight(600, false);
|
|
467
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
468
|
+
expectScrolling(true);
|
|
469
|
+
(0, chai_1.expect)(updateOptions.callCount).to.equal(2);
|
|
470
|
+
contentHeight = 400;
|
|
471
|
+
changed.fire();
|
|
472
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${400 + chrome}px`);
|
|
473
|
+
expectScrolling(false);
|
|
474
|
+
entry.setAvailableHeight(200, false);
|
|
475
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${400 + chrome}px`);
|
|
476
|
+
expectScrolling(false);
|
|
477
|
+
contentHeight = 5000 - chrome;
|
|
478
|
+
changed.fire();
|
|
479
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
480
|
+
expectScrolling(false);
|
|
481
|
+
contentHeight++;
|
|
482
|
+
changed.fire();
|
|
483
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
484
|
+
expectScrolling(true);
|
|
485
|
+
entry.setCollapsed(true);
|
|
486
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${chrome}px`);
|
|
487
|
+
entry.setAvailableHeight(100, false);
|
|
488
|
+
contentHeight = 20;
|
|
489
|
+
changed.fire();
|
|
490
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${chrome}px`);
|
|
491
|
+
entry.setCollapsed(false);
|
|
492
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${20 + chrome}px`);
|
|
493
|
+
expectScrolling(false);
|
|
494
|
+
if (isDiff) {
|
|
495
|
+
originalHeight = 500;
|
|
496
|
+
originalChanged.fire();
|
|
497
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${500 + chrome}px`);
|
|
498
|
+
expectScrolling(false);
|
|
499
|
+
originalHeight = 10;
|
|
500
|
+
originalChanged.fire();
|
|
501
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal(`${20 + chrome}px`);
|
|
502
|
+
expectScrolling(false);
|
|
503
|
+
}
|
|
504
|
+
entry.setAvailableHeight(500);
|
|
505
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('500px');
|
|
506
|
+
expectScrolling(false);
|
|
507
|
+
contentHeight = 600;
|
|
508
|
+
changed.fire();
|
|
509
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('500px');
|
|
510
|
+
expectScrolling(true);
|
|
511
|
+
entry.setAvailableHeight(700);
|
|
512
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('700px');
|
|
513
|
+
expectScrolling(false);
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
entry.dispose();
|
|
517
|
+
changed.dispose();
|
|
518
|
+
originalChanged.dispose();
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
describe('DiffEntryLoadingWidget / DiffEntryErrorWidget', () => {
|
|
524
|
+
it('should render a spinning codicon loading indicator', () => {
|
|
525
|
+
const w = new multi_diff_editor_1.DiffEntryLoadingWidget();
|
|
526
|
+
try {
|
|
527
|
+
const spinner = w.node.querySelector('.codicon-loading.codicon-modifier-spin');
|
|
528
|
+
(0, chai_1.expect)(spinner).to.exist;
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
w.dispose();
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
it('should render the error message', () => {
|
|
535
|
+
const w = new multi_diff_editor_1.DiffEntryErrorWidget('oops');
|
|
536
|
+
try {
|
|
537
|
+
(0, chai_1.expect)(w.node.textContent).to.contain('oops');
|
|
538
|
+
(0, chai_1.expect)(w.node.querySelector('.codicon-error')).to.exist;
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
w.dispose();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe('MultiDiffEditor', () => {
|
|
546
|
+
function createEditor(count, load) {
|
|
547
|
+
const container = new inversify_1.Container();
|
|
548
|
+
container.bind(multi_diff_editor_1.MultiDiffEditorData).toConstantValue({ title: 'Changes', resources: Array(count).fill(sampleResource) });
|
|
549
|
+
container.bind(multi_diff_editor_1.MultiDiffEditor).toSelf();
|
|
550
|
+
const editor = container.get(multi_diff_editor_1.MultiDiffEditor);
|
|
551
|
+
for (let index = 0; index < count; index++) {
|
|
552
|
+
editor.addDiffEntry(sampleResource, new multi_diff_editor_1.DiffEntryHeaderWidget(sampleResource, mockLabelProvider), load);
|
|
553
|
+
}
|
|
554
|
+
return editor;
|
|
555
|
+
}
|
|
556
|
+
it('should opt out of persistence and ignore restore state', () => {
|
|
557
|
+
const editor = createEditor(1);
|
|
558
|
+
try {
|
|
559
|
+
editor.restoreState({ collapsedUris: [sampleResource.modifiedUri.toString()], scrollTop: 123 });
|
|
560
|
+
(0, chai_1.expect)(editor.entries[0].isCollapsed).to.be.false;
|
|
561
|
+
(0, chai_1.expect)(editor['entriesPanel'].node.scrollTop).to.equal(0);
|
|
562
|
+
(0, chai_1.expect)(editor.storeState()).to.be.undefined;
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
editor.dispose();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
it('should observe near-viewport entries, skip collapsed entries, and disconnect safely', async () => {
|
|
569
|
+
const previous = globalThis.IntersectionObserver;
|
|
570
|
+
let callback;
|
|
571
|
+
const observe = sinon.spy();
|
|
572
|
+
const disconnect = sinon.spy();
|
|
573
|
+
let options;
|
|
574
|
+
globalThis.IntersectionObserver = class {
|
|
575
|
+
constructor(cb, opts) {
|
|
576
|
+
this.observe = observe;
|
|
577
|
+
this.disconnect = disconnect;
|
|
578
|
+
callback = cb;
|
|
579
|
+
options = opts;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const load = sinon.stub().callsFake(async () => new browser_1.Widget());
|
|
583
|
+
const editor = createEditor(2, load);
|
|
584
|
+
const firstLoad = sinon.spy(editor.entries[0], 'loadEditor');
|
|
585
|
+
const secondLoad = sinon.spy(editor.entries[1], 'loadEditor');
|
|
586
|
+
try {
|
|
587
|
+
(0, chai_1.expect)(observe.callCount).to.equal(2);
|
|
588
|
+
(0, chai_1.expect)(options?.root).to.equal(editor['entriesPanel'].node);
|
|
589
|
+
(0, chai_1.expect)(options?.rootMargin).to.equal('500px 0px');
|
|
590
|
+
editor.entries[1].setCollapsed(true);
|
|
591
|
+
const records = editor.entries.map(entry => ({ target: entry.node, isIntersecting: true }));
|
|
592
|
+
callback([{ ...records[0], isIntersecting: false }], editor['entryObserver']);
|
|
593
|
+
(0, chai_1.expect)(firstLoad.called).to.be.false;
|
|
594
|
+
callback(records, editor['entryObserver']);
|
|
595
|
+
(0, chai_1.expect)(firstLoad.calledOnce).to.be.true;
|
|
596
|
+
(0, chai_1.expect)(editor.entries[1].editorWidget).to.be.undefined;
|
|
597
|
+
(0, chai_1.expect)(load.calledOnce).to.be.true;
|
|
598
|
+
await Promise.resolve();
|
|
599
|
+
(0, chai_1.expect)(editor.entries[0].editorWidget).to.exist;
|
|
600
|
+
editor.entries[1].setCollapsed(false);
|
|
601
|
+
await Promise.resolve();
|
|
602
|
+
(0, chai_1.expect)(load.calledTwice).to.be.true;
|
|
603
|
+
(0, chai_1.expect)(editor.entries[1].editorWidget).to.exist;
|
|
604
|
+
editor.dispose();
|
|
605
|
+
(0, chai_1.expect)(disconnect.calledOnce).to.be.true;
|
|
606
|
+
callback(records, editor['entryObserver']);
|
|
607
|
+
(0, chai_1.expect)(firstLoad.calledOnce).to.be.true;
|
|
608
|
+
(0, chai_1.expect)(secondLoad.calledTwice).to.be.true;
|
|
609
|
+
(0, chai_1.expect)(load.calledTwice).to.be.true;
|
|
610
|
+
}
|
|
611
|
+
finally {
|
|
612
|
+
editor.dispose();
|
|
613
|
+
globalThis.IntersectionObserver = previous;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
it('should expand and load explicitly revealed entries', () => {
|
|
617
|
+
const previous = globalThis.requestAnimationFrame;
|
|
618
|
+
globalThis.requestAnimationFrame = sinon.stub().returns(0);
|
|
619
|
+
const editor = createEditor(1);
|
|
620
|
+
const load = sinon.spy(editor.entries[0], 'loadEditor');
|
|
621
|
+
try {
|
|
622
|
+
editor.entries[0].setCollapsed(true);
|
|
623
|
+
editor.revealResource(sampleResource.modifiedUri);
|
|
624
|
+
(0, chai_1.expect)(editor.entries[0].isCollapsed).to.be.false;
|
|
625
|
+
(0, chai_1.expect)(load.called).to.be.true;
|
|
626
|
+
}
|
|
627
|
+
finally {
|
|
628
|
+
editor.dispose();
|
|
629
|
+
globalThis.requestAnimationFrame = previous;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
it('should reveal only within the entries panel and repeat after an in-flight target loads', async () => {
|
|
633
|
+
const previous = globalThis.requestAnimationFrame;
|
|
634
|
+
const frames = [];
|
|
635
|
+
globalThis.requestAnimationFrame = callback => frames.push(callback);
|
|
636
|
+
let resolve;
|
|
637
|
+
const editor = createEditor(1, () => new Promise(r => resolve = r));
|
|
638
|
+
const entry = editor.entries[0];
|
|
639
|
+
const panel = editor['entriesPanel'].node;
|
|
640
|
+
const offset = sinon.stub(entry.node, 'offsetTop').get(() => 120);
|
|
641
|
+
const pending = entry.loadEditor();
|
|
642
|
+
try {
|
|
643
|
+
browser_1.Widget.attach(editor, document.body);
|
|
644
|
+
editor.node.scrollTop = 42;
|
|
645
|
+
document.body.scrollTop = 17;
|
|
646
|
+
editor.revealResource(entry.modifiedUri);
|
|
647
|
+
frames.splice(0).forEach(frame => frame(0));
|
|
648
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(120);
|
|
649
|
+
(0, chai_1.expect)(editor.node.scrollTop).to.equal(42);
|
|
650
|
+
(0, chai_1.expect)(document.body.scrollTop).to.equal(17);
|
|
651
|
+
offset.get(() => 480);
|
|
652
|
+
resolve(new browser_1.Widget());
|
|
653
|
+
await pending;
|
|
654
|
+
frames.splice(0).forEach(frame => frame(0));
|
|
655
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(480);
|
|
656
|
+
(0, chai_1.expect)(editor.node.scrollTop).to.equal(42);
|
|
657
|
+
(0, chai_1.expect)(document.body.scrollTop).to.equal(17);
|
|
658
|
+
}
|
|
659
|
+
finally {
|
|
660
|
+
editor.dispose();
|
|
661
|
+
offset.restore();
|
|
662
|
+
document.body.scrollTop = 0;
|
|
663
|
+
globalThis.requestAnimationFrame = previous;
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
it('should ignore stale reveal frames and frames after disposal', () => {
|
|
667
|
+
const previous = globalThis.requestAnimationFrame;
|
|
668
|
+
const frames = [];
|
|
669
|
+
globalThis.requestAnimationFrame = callback => frames.push(callback);
|
|
670
|
+
const editor = createEditor(1);
|
|
671
|
+
const otherResource = { ...sampleResource, modifiedUri: new core_1.URI('file:///workspace/c.ts') };
|
|
672
|
+
const other = editor.addDiffEntry(otherResource, new multi_diff_editor_1.DiffEntryHeaderWidget(otherResource, mockLabelProvider));
|
|
673
|
+
const panel = editor['entriesPanel'].node;
|
|
674
|
+
const offset = sinon.stub(other.node, 'offsetTop').get(() => 240);
|
|
675
|
+
try {
|
|
676
|
+
browser_1.Widget.attach(editor, document.body);
|
|
677
|
+
editor.revealResource(sampleResource.modifiedUri);
|
|
678
|
+
editor.revealResource(otherResource.modifiedUri);
|
|
679
|
+
frames.shift()(0);
|
|
680
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(0);
|
|
681
|
+
frames.shift()(0);
|
|
682
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(240);
|
|
683
|
+
editor.revealResource(sampleResource.modifiedUri);
|
|
684
|
+
editor.dispose();
|
|
685
|
+
frames.splice(0).forEach(frame => frame(0));
|
|
686
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(240);
|
|
687
|
+
}
|
|
688
|
+
finally {
|
|
689
|
+
editor.dispose();
|
|
690
|
+
offset.restore();
|
|
691
|
+
globalThis.requestAnimationFrame = previous;
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
it('should activate a visible editor, remember focused entries, and preserve ancestor scroll positions', () => {
|
|
695
|
+
const editor = createEditor(2);
|
|
696
|
+
const panel = editor['entriesPanel'].node;
|
|
697
|
+
sinon.stub(panel, 'getBoundingClientRect').returns({ top: 0, bottom: 500 });
|
|
698
|
+
const firstBounds = sinon.stub(editor.entries[0].node, 'getBoundingClientRect').returns({ top: -400, bottom: -100 });
|
|
699
|
+
sinon.stub(editor.entries[1].node, 'getBoundingClientRect').returns({ top: 100, bottom: 400 });
|
|
700
|
+
class FocusWidget extends browser_1.Widget {
|
|
701
|
+
onActivateRequest() {
|
|
702
|
+
panel.scrollTop = 0;
|
|
703
|
+
editor.node.scrollTop = 0;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const first = new FocusWidget();
|
|
707
|
+
const second = new FocusWidget();
|
|
708
|
+
editor.entries[0].setEditor(first);
|
|
709
|
+
editor.entries[1].setEditor(second);
|
|
710
|
+
const firstActivate = sinon.spy(first, 'processMessage');
|
|
711
|
+
const secondActivate = sinon.spy(second, 'processMessage');
|
|
712
|
+
try {
|
|
713
|
+
panel.scrollTop = 350;
|
|
714
|
+
editor.node.scrollTop = 42;
|
|
715
|
+
browser_1.MessageLoop.sendMessage(editor, browser_1.Widget.Msg.ActivateRequest);
|
|
716
|
+
(0, chai_1.expect)(firstActivate.calledWith(browser_1.Widget.Msg.ActivateRequest)).to.be.false;
|
|
717
|
+
(0, chai_1.expect)(secondActivate.calledWith(browser_1.Widget.Msg.ActivateRequest)).to.be.true;
|
|
718
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(350);
|
|
719
|
+
(0, chai_1.expect)(editor.node.scrollTop).to.equal(42);
|
|
720
|
+
second.node.dispatchEvent(new window.Event('focusin', { bubbles: true }));
|
|
721
|
+
firstBounds.returns({ top: 0, bottom: 200 });
|
|
722
|
+
secondActivate.resetHistory();
|
|
723
|
+
browser_1.MessageLoop.sendMessage(editor, browser_1.Widget.Msg.ActivateRequest);
|
|
724
|
+
(0, chai_1.expect)(secondActivate.calledWith(browser_1.Widget.Msg.ActivateRequest)).to.be.true;
|
|
725
|
+
editor.entries[1].setCollapsed(true);
|
|
726
|
+
browser_1.MessageLoop.sendMessage(editor, browser_1.Widget.Msg.ActivateRequest);
|
|
727
|
+
(0, chai_1.expect)(panel.scrollTop).to.equal(350);
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
sinon.restore();
|
|
731
|
+
editor.dispose();
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
it('should fill the viewport for a single entry and follow resizes', () => {
|
|
735
|
+
const editor = createEditor(1);
|
|
736
|
+
try {
|
|
737
|
+
browser_1.MessageLoop.sendMessage(editor, new browser_1.Widget.ResizeMessage(1000, 800));
|
|
738
|
+
(0, chai_1.expect)(editor.entries[0].node.style.height).to.equal('800px');
|
|
739
|
+
browser_1.MessageLoop.sendMessage(editor, new browser_1.Widget.ResizeMessage(1000, 400));
|
|
740
|
+
(0, chai_1.expect)(editor.entries[0].node.style.height).to.equal('400px');
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
editor.dispose();
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
it('should retain bounded content heights for multiple entries', () => {
|
|
747
|
+
const editor = createEditor(2);
|
|
748
|
+
try {
|
|
749
|
+
browser_1.MessageLoop.sendMessage(editor, new browser_1.Widget.ResizeMessage(1000, 800));
|
|
750
|
+
for (const entry of editor.entries) {
|
|
751
|
+
entry.setHeight(1000);
|
|
752
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('1000px');
|
|
753
|
+
entry.setHeight(10_000);
|
|
754
|
+
(0, chai_1.expect)(entry.node.style.height).to.equal('5000px');
|
|
755
|
+
}
|
|
756
|
+
browser_1.MessageLoop.sendMessage(editor, new browser_1.Widget.ResizeMessage(1000, 300));
|
|
757
|
+
(0, chai_1.expect)(editor.entries[0].node.style.height).to.equal('5000px');
|
|
758
|
+
editor.entries[1].setHeight(60);
|
|
759
|
+
(0, chai_1.expect)(editor.entries[1].node.style.height).to.equal('60px');
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
editor.dispose();
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
describe('MultiDiffEditorWidgetFactory', () => {
|
|
767
|
+
class TestFactory extends multi_diff_editor_widget_factory_1.MultiDiffEditorWidgetFactory {
|
|
768
|
+
configure(widget) {
|
|
769
|
+
this.configureEmbeddedEditor(widget);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
it('should disable visibility side effects and enable unchanged-region hiding only for diff editors', () => {
|
|
773
|
+
const factory = new TestFactory(new inversify_1.Container(), {}, mockLabelProvider);
|
|
774
|
+
const diff = Object.create(monaco_diff_editor_1.MonacoDiffEditor.prototype);
|
|
775
|
+
const updateDiffOptions = sinon.spy();
|
|
776
|
+
Object.defineProperty(diff, 'diffEditor', { value: { updateOptions: updateDiffOptions } });
|
|
777
|
+
const code = Object.create(monaco_editor_1.MonacoEditor.prototype);
|
|
778
|
+
const updateCodeOptions = sinon.spy();
|
|
779
|
+
code.getControl = () => ({ updateOptions: updateCodeOptions });
|
|
780
|
+
factory.configure({ editor: diff });
|
|
781
|
+
factory.configure({ editor: code });
|
|
782
|
+
for (const editor of [diff, code]) {
|
|
783
|
+
(0, chai_1.expect)(editor.detachModelOnHide).to.be.false;
|
|
784
|
+
}
|
|
785
|
+
(0, chai_1.expect)(updateDiffOptions.firstCall.args[0]).to.deep.equal({
|
|
786
|
+
folding: false, codeLens: false, scrollBeyondLastLine: false, stickyScroll: { enabled: false }, minimap: { enabled: false }, hideUnchangedRegions: { enabled: true }
|
|
787
|
+
});
|
|
788
|
+
(0, chai_1.expect)(updateCodeOptions.firstCall.args[0]).not.to.have.property('hideUnchangedRegions');
|
|
789
|
+
});
|
|
790
|
+
it('should return placeholders without loading models, then configure and publish loaded editors', async () => {
|
|
791
|
+
const widget = new browser_1.Widget();
|
|
792
|
+
const createByUri = sinon.stub().resolves(widget);
|
|
793
|
+
const manager = { createByUri };
|
|
794
|
+
const factory = new multi_diff_editor_widget_factory_1.MultiDiffEditorWidgetFactory(new inversify_1.Container(), manager, mockLabelProvider);
|
|
795
|
+
const editor = factory.createMultiDiffEditor(multi_diff_editor_uri_1.MultiDiffEditorUri.encode({ title: 'Changes', resources: [sampleResource] }));
|
|
796
|
+
const changed = sinon.spy();
|
|
797
|
+
editor.onDidChangeTrackableWidgets(changed);
|
|
798
|
+
try {
|
|
799
|
+
(0, chai_1.expect)(createByUri.called).to.be.false;
|
|
800
|
+
(0, chai_1.expect)(editor.entries[0].node.querySelector('.multi-diff-entry-loading')).to.exist;
|
|
801
|
+
await editor.entries[0].loadEditor();
|
|
802
|
+
(0, chai_1.expect)(createByUri.calledOnce).to.be.true;
|
|
803
|
+
(0, chai_1.expect)(editor.getTrackableWidgets()).to.deep.equal([widget]);
|
|
804
|
+
(0, chai_1.expect)(changed.calledOnce).to.be.true;
|
|
805
|
+
}
|
|
806
|
+
finally {
|
|
807
|
+
editor.dispose();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
describe('MultiDiffEditorLabelProvider', () => {
|
|
812
|
+
it('should label synthetic URIs with the full editor title', () => {
|
|
813
|
+
const provider = new multi_diff_editor_1.MultiDiffEditorLabelProvider();
|
|
814
|
+
const uri = multi_diff_editor_uri_1.MultiDiffEditorUri.encode({ title: 'Changes in feature/test', resources: [] });
|
|
815
|
+
(0, chai_1.expect)(provider.canHandle(uri)).to.equal(20);
|
|
816
|
+
(0, chai_1.expect)(provider.canHandle(new core_1.URI('file:///a.ts'))).to.equal(0);
|
|
817
|
+
(0, chai_1.expect)(provider.getName(uri)).to.equal('Changes in feature/test');
|
|
818
|
+
(0, chai_1.expect)(provider.getLongName(uri)).to.equal('Changes in feature/test');
|
|
819
|
+
(0, chai_1.expect)(provider.getIcon()).to.equal('codicon codicon-diff-multiple');
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
describe('MultiDiffEditorOpenHandler', () => {
|
|
823
|
+
// canHandle can be exercised without DI, since it's a pure function of the URI.
|
|
824
|
+
const handler = Object.create(multi_diff_editor_1.MultiDiffEditorOpenHandler.prototype);
|
|
825
|
+
it('should return 1000 for multi-diff-editor URIs', () => {
|
|
826
|
+
const uri = multi_diff_editor_uri_1.MultiDiffEditorUri.encode({ title: 'x', resources: [] });
|
|
827
|
+
(0, chai_1.expect)(handler.canHandle(uri)).to.equal(1000);
|
|
828
|
+
});
|
|
829
|
+
it('should return 0 for other URIs', () => {
|
|
830
|
+
(0, chai_1.expect)(handler.canHandle(new core_1.URI('file:///a.ts'))).to.equal(0);
|
|
831
|
+
(0, chai_1.expect)(handler.canHandle(new core_1.URI('diff://a/b'))).to.equal(0);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
//# sourceMappingURL=multi-diff-editor-widget.spec.js.map
|