addons/vscode/extension/__tests__/VSCodeRepo.test.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Repository} from 'isl-server/src/Repository';
b69ab319import type {ServerPlatform} from 'isl-server/src/serverPlatform';
b69ab3110import type {RepositoryContext} from 'isl-server/src/serverTypes';
b69ab3111import type {RepoInfo, ValidatedRepoInfo} from 'isl/src/types';
b69ab3112import type {EnabledSCMApiFeature} from '../types';
b69ab3113
b69ab3114import {repositoryCache} from 'isl-server/src/RepositoryCache';
b69ab3115import {makeServerSideTracker} from 'isl-server/src/analytics/serverSideTracker';
b69ab3116import {Logger} from 'isl-server/src/logger';
b69ab3117import {TypedEventEmitter} from 'shared/TypedEventEmitter';
b69ab3118import {nextTick} from 'shared/testUtils';
b69ab3119import * as vscode from 'vscode';
b69ab3120import {VSCodeReposList} from '../VSCodeRepo';
b69ab3121
b69ab3122export class MockLogger extends Logger {
b69ab3123 write() {
b69ab3124 // noop
b69ab3125 }
b69ab3126}
b69ab3127export const mockLogger = new MockLogger();
b69ab3128
b69ab3129const mockTracker = makeServerSideTracker(
b69ab3130 mockLogger,
b69ab3131 {platformName: 'test'} as ServerPlatform,
b69ab3132 '0.1',
b69ab3133 jest.fn(),
b69ab3134);
b69ab3135
b69ab3136jest.mock('isl-server/src/Repository', () => {
b69ab3137 class MockRepository implements Partial<Repository> {
b69ab3138 static getRepoInfo = jest.fn((ctx: RepositoryContext): Promise<RepoInfo> => {
b69ab3139 let root: string;
b69ab3140 // resolve cwd into shared mock locations
b69ab3141 if (ctx.cwd.includes('/path/to/repo1')) {
b69ab3142 root = '/path/to/repo1';
b69ab3143 } else if (ctx.cwd.includes('/path/to/repo2')) {
b69ab3144 root = '/path/to/repo2';
b69ab3145 } else {
b69ab3146 return Promise.resolve({type: 'cwdNotARepository', cwd: ctx.cwd});
b69ab3147 }
b69ab3148 return Promise.resolve({
b69ab3149 type: 'success',
b69ab3150 repoRoot: root,
b69ab3151 dotdir: root + '/.sl',
b69ab3152 command: 'sl',
b69ab3153 preferredSubmitCommand: 'pr',
b69ab3154 codeReviewSystem: {type: 'unknown', path: ''},
b69ab3155 pullRequestDomain: undefined,
b69ab3156 isEdenFs: false,
b69ab3157 });
b69ab3158 });
b69ab3159 constructor(
b69ab3160 public info: ValidatedRepoInfo,
b69ab3161 public logger?: Logger,
b69ab3162 ) {}
b69ab3163
b69ab3164 public disposables: Array<() => void> = [];
b69ab3165 public dispose() {
b69ab3166 this.disposables.forEach(d => d());
b69ab3167 }
b69ab3168 public onDidDispose = (cb: () => void) => this.disposables.push(cb);
b69ab3169 public subscribeToUncommittedChanges = jest.fn();
b69ab3170 public onChangeConflictState = jest.fn();
b69ab3171 public getUncommittedChanges = jest.fn();
b69ab3172 public getMergeConflicts = jest.fn();
b69ab3173 }
b69ab3174 return {
b69ab3175 Repository: MockRepository as unknown as Repository,
b69ab3176 };
b69ab3177});
b69ab3178
b69ab3179describe('adding and removing repositories', () => {
b69ab3180 let foldersEmitter: TypedEventEmitter<'value', vscode.WorkspaceFoldersChangeEvent>;
b69ab3181 beforeEach(() => {
b69ab3182 foldersEmitter = new TypedEventEmitter();
b69ab3183 (vscode.workspace.onDidChangeWorkspaceFolders as jest.Mock).mockImplementation(cb => {
b69ab3184 foldersEmitter.on('value', cb);
b69ab3185 return {dispose: () => foldersEmitter.off('value', cb)};
b69ab3186 });
b69ab3187 });
b69ab3188
b69ab3189 afterEach(() => {
b69ab3190 jest.clearAllMocks();
b69ab3191 repositoryCache.clearCache();
b69ab3192 foldersEmitter.removeAllListeners();
b69ab3193 });
b69ab3194
b69ab3195 const ENABLED = new Set<EnabledSCMApiFeature>(['blame', 'sidebar']);
b69ab3196
b69ab3197 it('creates repositories for workspace folders', async () => {
b69ab3198 const repos = new VSCodeReposList(mockLogger, mockTracker, ENABLED);
b69ab3199 foldersEmitter.emit('value', {
b69ab31100 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1')}],
b69ab31101 removed: [],
b69ab31102 });
b69ab31103 await nextTick();
b69ab31104
b69ab31105 expect(vscode.scm.createSourceControl).toHaveBeenCalledTimes(1);
b69ab31106 repos.dispose();
b69ab31107 });
b69ab31108
b69ab31109 it('deduplicates among shared repos', async () => {
b69ab31110 const repos = new VSCodeReposList(mockLogger, mockTracker, ENABLED);
b69ab31111 foldersEmitter.emit('value', {
b69ab31112 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31113 removed: [],
b69ab31114 });
b69ab31115 await nextTick();
b69ab31116 foldersEmitter.emit('value', {
b69ab31117 added: [{name: 'my folder', index: 1, uri: vscode.Uri.file('/path/to/repo1/bar')}],
b69ab31118 removed: [],
b69ab31119 });
b69ab31120 await nextTick();
b69ab31121
b69ab31122 expect(vscode.scm.createSourceControl).toHaveBeenCalledTimes(1);
b69ab31123
b69ab31124 foldersEmitter.emit('value', {
b69ab31125 added: [{name: 'my folder', index: 1, uri: vscode.Uri.file('/path/to/repo2/foobar')}],
b69ab31126 removed: [],
b69ab31127 });
b69ab31128 await nextTick();
b69ab31129 expect(vscode.scm.createSourceControl).toHaveBeenCalledTimes(2);
b69ab31130
b69ab31131 repos.dispose();
b69ab31132 });
b69ab31133
b69ab31134 it('deletes repositories for workspace folders', async () => {
b69ab31135 const repos = new VSCodeReposList(mockLogger, mockTracker, ENABLED);
b69ab31136
b69ab31137 // add repo twice, only creates 1 repo
b69ab31138 foldersEmitter.emit('value', {
b69ab31139 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31140 removed: [],
b69ab31141 });
b69ab31142 await nextTick();
b69ab31143 foldersEmitter.emit('value', {
b69ab31144 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1/bar')}],
b69ab31145 removed: [],
b69ab31146 });
b69ab31147 await nextTick();
b69ab31148 expect(vscode.scm.createSourceControl).toHaveBeenCalledTimes(1);
b69ab31149
b69ab31150 // remove that repo
b69ab31151 foldersEmitter.emit('value', {
b69ab31152 added: [],
b69ab31153 removed: [{name: 'my folder', index: 1, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31154 });
b69ab31155 await nextTick();
b69ab31156 foldersEmitter.emit('value', {
b69ab31157 added: [],
b69ab31158 removed: [{name: 'my folder', index: 1, uri: vscode.Uri.file('/path/to/repo1/bar')}],
b69ab31159 });
b69ab31160 await nextTick();
b69ab31161
b69ab31162 // adding the same repo again must create it again
b69ab31163 foldersEmitter.emit('value', {
b69ab31164 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31165 removed: [],
b69ab31166 });
b69ab31167 await nextTick();
b69ab31168 expect(vscode.scm.createSourceControl).toHaveBeenCalledTimes(2);
b69ab31169
b69ab31170 repos.dispose();
b69ab31171 });
b69ab31172
b69ab31173 it('looks up repos by prefix', async () => {
b69ab31174 const repos = new VSCodeReposList(mockLogger, mockTracker, ENABLED);
b69ab31175
b69ab31176 foldersEmitter.emit('value', {
b69ab31177 added: [{name: 'my folder', index: 0, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31178 removed: [],
b69ab31179 });
b69ab31180 await nextTick();
b69ab31181
b69ab31182 expect(repos.repoForPath('/path/to/repo1/foo')).not.toBeUndefined();
b69ab31183 expect(repos.repoForPath('/path/to/repo1/foo/myFile.txt')).not.toBeUndefined();
b69ab31184 expect(repos.repoForPath('/path/to/repo2/foo')).toBeUndefined();
b69ab31185
b69ab31186 foldersEmitter.emit('value', {
b69ab31187 added: [],
b69ab31188 removed: [{name: 'my folder', index: 1, uri: vscode.Uri.file('/path/to/repo1/foo')}],
b69ab31189 });
b69ab31190 await nextTick();
b69ab31191
b69ab31192 expect(repos.repoForPath('/path/to/repo1/foo')).toBeUndefined();
b69ab31193 expect(repos.repoForPath('/path/to/repo1/foo/myFile.txt')).toBeUndefined();
b69ab31194
b69ab31195 repos.dispose();
b69ab31196 });
b69ab31197});