diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 0d5f4528f05b..86dbc6b19e00 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -7,6 +7,7 @@ import { ValueWithChangeEvent } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { observableFromEvent, ValueWithChangeEventFromObservable, waitForState } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/path.js'; +import { comparePaths } from '../../../../base/common/comparers.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IMultiDiffEditorOptions } from '../../../../editor/common/multiDiffEditor.js'; import { localize2 } from '../../../../nls.js'; @@ -176,7 +177,9 @@ class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource { ) { this._resources = observableFromEvent( this._group.onDidChangeResources, - () => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri)) + () => /** @description resources */[...this._group.resources] + .sort((a, b) => comparePaths(a.sourceUri.fsPath, b.sourceUri.fsPath)) + .map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri)) ); this.resources = new ValueWithChangeEventFromObservable(this._resources); this.contextKeys = { diff --git a/src/vs/workbench/contrib/multiDiffEditor/test/browser/scmMultiDiffSourceResolver.test.ts b/src/vs/workbench/contrib/multiDiffEditor/test/browser/scmMultiDiffSourceResolver.test.ts index 313da4a454d0..7fa1520e0956 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/test/browser/scmMultiDiffSourceResolver.test.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/test/browser/scmMultiDiffSourceResolver.test.ts @@ -4,14 +4,82 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Emitter } from '../../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; import { observableValue } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { mock } from '../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IActivityService } from '../../../../services/activity/common/activity.js'; import { ISCMHistoryProvider } from '../../../scm/common/history.js'; -import { ISCMProvider, ISCMRepository, ISCMService } from '../../../scm/common/scm.js'; -import { ScmHistoryItemResolver } from '../../browser/scmMultiDiffSourceResolver.js'; +import { ISCMProvider, ISCMRepository, ISCMResource, ISCMResourceGroup, ISCMService } from '../../../scm/common/scm.js'; +import { ScmMultiDiffSourceResolver, ScmHistoryItemResolver } from '../../browser/scmMultiDiffSourceResolver.js'; + +suite('ScmMultiDiffSourceResolver', () => { + + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + test('sorts resources by path', async () => { + const resources: ISCMResource[] = []; + const onDidChangeResources = disposables.add(new Emitter()); + const group = new class extends mock() { + override readonly id = 'changes'; + override readonly label = 'Changes'; + override readonly resources = resources; + override readonly onDidChangeResources = onDidChangeResources.event; + }(); + const provider = new class extends mock() { + override readonly id = 'scm0'; + override readonly providerId = 'scm0'; + override readonly rootUri = URI.file('/repository'); + override readonly groups = [group]; + override readonly onDidChangeResourceGroups = new Emitter().event; + }(); + const repository = new class extends mock() { + override readonly id = provider.id; + override readonly provider = provider; + }(); + const repositories = [repository]; + const onDidAddRepository = disposables.add(new Emitter()); + const scmService = new class extends mock() { + override readonly onDidAddRepository = onDidAddRepository.event; + override get repositories(): Iterable { return repositories; } + override get repositoryCount(): number { return repositories.length; } + override getRepository(idOrResource: string | URI): ISCMRepository | undefined { + return typeof idOrResource === 'string' ? repositories.find(r => r.id === idOrResource) : undefined; + } + }(); + + const activityService = new class extends mock() { + override readonly onDidChangeActivity = Event.None; + override getViewContainerActivities() { return []; } + }(); + const resolver = new ScmMultiDiffSourceResolver(scmService, activityService); + const sourceUri = ScmMultiDiffSourceResolver.getMultiDiffSourceUri(provider.rootUri.toString(), group.id); + const sourcePromise = resolver.resolveDiffSource(sourceUri); + + const createResource = (path: string) => new class extends mock() { + override readonly sourceUri = URI.file(path); + override readonly multiDiffEditorModifiedUri = this.sourceUri; + }(); + + resources.push( + createResource('/repository/githubServer.ts'), + createResource('/repository/env.ts'), + createResource('/repository/githubUri.ts'), + ); + onDidChangeResources.fire(); + + const source = await sourcePromise; + assert.deepStrictEqual( + source.resources.value.map(resource => resource.goToFileUri?.path), + [ + '/repository/env.ts', + '/repository/githubServer.ts', + '/repository/githubUri.ts', + ] + ); + }); +}); suite('ScmHistoryItemResolver', () => {