diff --git a/src/commands.ts b/src/commands.ts index 99dde41..2bfa1a1 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1089,7 +1089,7 @@ export class CommandCenter { const items = await repository.stashList(); const stashId = await interaction.pickStashItem(items, operation); if (stashId) { - repository.stashApplyOrDrop(operation, stashId); + return repository.stashApplyOrDrop(operation, stashId); } } diff --git a/src/openedRepository.ts b/src/openedRepository.ts index ce025ad..86c7341 100644 --- a/src/openedRepository.ts +++ b/src/openedRepository.ts @@ -410,7 +410,7 @@ export class OpenedRepository { * paths[] will cause damage */ async cleanAll(): Promise { - this.exec(['clean']); + await this.exec(['clean']); } async ignore(paths: RelativePath[]): Promise { diff --git a/src/repository.ts b/src/repository.ts index 4cdc445..b4d1dbe 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -693,12 +693,13 @@ export class Repository implements IDisposable, InteractionAPI { return this.stagingGroup.resourceStates.map(r => this.mapResourceToRepoRelativePath(r) ); - } else if (scope === CommitScope.WORKING_GROUP) { + } + if (scope === CommitScope.WORKING_GROUP) { return this.workingGroup.resourceStates.map(r => this.mapResourceToRepoRelativePath(r) ); } - return []; + return []; // scope === CommitScope.ALL } @throttle @@ -897,10 +898,13 @@ export class Repository implements IDisposable, InteractionAPI { scope: Exclude, operation: 'save' | 'snapshot' ): Promise { - return this.runWithProgress(Operation.Commit, async () => { - const fileList = this.scopeToFileList(scope); - this.repository.stash(message, operation, fileList); - }); + return this.runWithProgress(Operation.Commit, async () => + this.repository.stash( + message, + operation, + this.scopeToFileList(scope) + ) + ); } async stashList(): Promise { diff --git a/src/test/suite/Infrastructure.test.ts b/src/test/suite/Infrastructure.test.ts index 6519c7e..0832027 100644 --- a/src/test/suite/Infrastructure.test.ts +++ b/src/test/suite/Infrastructure.test.ts @@ -41,7 +41,7 @@ suite('Infrastructure', () => { }); sinon.assert.calledOnceWithExactly( showErrorMessage, - 'Fossil: fossil: unknown command: fizzbuzz', + `Fossil: ${fossilPath}: unknown command: fizzbuzz`, 'Open Fossil Log' ); }); diff --git a/src/test/suite/commandSuites.ts b/src/test/suite/commandSuites.ts index c3cce6e..510eeb1 100644 --- a/src/test/suite/commandSuites.ts +++ b/src/test/suite/commandSuites.ts @@ -14,7 +14,7 @@ import { import * as assert from 'assert/strict'; import * as fs from 'fs/promises'; import { OpenedRepository, ResourceStatus } from '../../openedRepository'; -import { Suite, Func, Test } from 'mocha'; +import { Suite, before, Func, Test } from 'mocha'; import { toFossilUri } from '../../uri'; declare module 'mocha' { @@ -46,11 +46,9 @@ export function StatusSuite(this: Suite): void { await fs.unlink(path.fsPath); const repository = getRepository(); await repository.updateModelState(); - assertGroups( - repository, - new Map([[path.fsPath, ResourceStatus.MISSING]]), - new Map() - ); + assertGroups(repository, { + working: [[path.fsPath, ResourceStatus.MISSING]], + }); }).timeout(5000); test('Rename is visible in Source Control panel', async () => { @@ -60,7 +58,7 @@ export function StatusSuite(this: Suite): void { const newFilename = 'sriciscp-renamed.txt'; const oldUri = await add(oldFilename, 'test\n', `add ${oldFilename}`); await repository.updateModelState(); - assertGroups(repository, new Map(), new Map()); + assertGroups(repository, {}); const openedRepository: OpenedRepository = (repository as any) .repository; @@ -68,11 +66,9 @@ export function StatusSuite(this: Suite): void { await openedRepository.exec(['mv', oldFilename, newFilename, '--hard']); await repository.updateModelState(); const barPath = Uri.joinPath(oldUri, '..', newFilename).fsPath; - assertGroups( - repository, - new Map([[barPath, ResourceStatus.RENAMED]]), - new Map() - ); + assertGroups(repository, { + working: [[barPath, ResourceStatus.RENAMED]], + }); }).timeout(15000); test('Merge is visible in Source Control panel', async () => { @@ -100,14 +96,12 @@ export function StatusSuite(this: Suite): void { await openedRepository.exec(['update', 'trunk']); await openedRepository.exec(['merge', 'test_brunch']); await repository.updateModelState(); - assertGroups( - repository, - new Map([ + assertGroups(repository, { + working: [ [barPath, ResourceStatus.ADDED], [fooPath, ResourceStatus.MODIFIED], - ]), - new Map() - ); + ], + }); }).timeout(10000); test.if(process.platform != 'win32', 'Meta', async () => { @@ -186,51 +180,69 @@ export function StatusSuite(this: Suite): void { await fs.mkdir(not_file_path); await repository.updateModelState(); - assertGroups( - repository, - new Map([ + assertGroups(repository, { + working: [ [executable_path, ResourceStatus.MODIFIED], [unexec_path, ResourceStatus.MODIFIED], [symlink_path, ResourceStatus.MODIFIED], [unlink_path, ResourceStatus.MODIFIED], [not_file_path, ResourceStatus.MISSING], - ]), - new Map() - ); + ], + }); new Map(); await fs.rmdir(not_file_path); }).timeout(20000); const testRename = async ( - status: string, - before: string, - after: string + status: `${'RENAMED' | 'EDITED'} ${'a' | 'a.txt -> b'}.txt`, + before: 'a.txt', + after: 'a.txt' | 'b.txt', + resourceStatus: ResourceStatus ) => { const repository = getRepository(); const execStub = getExecStub(this.ctx.sandbox); await fakeFossilStatus(execStub, status); await repository.updateModelState(); - const folder = vscode.workspace.workspaceFolders![0].uri; - const uriBefore = Uri.joinPath(folder, before).toString(); - const uriAfter = Uri.joinPath(folder, after).toString(); - assert.equal(repository.workingGroup.resourceStates.length, 1); + const root = vscode.workspace.workspaceFolders![0].uri; + const uriBefore = Uri.joinPath(root, before); + const uriAfter = Uri.joinPath(root, after); + assertGroups(repository, { + working: [[uriAfter.fsPath, resourceStatus]], + }); const resource = repository.workingGroup.resourceStates[0]; - assert.equal(resource.resourceUri.toString(), uriAfter); - assert.equal(resource.original.toString(), uriBefore); + assert.equal(resource.original.toString(), uriBefore.toString()); assert.ok(resource.renameResourceUri); - assert.equal(resource.renameResourceUri.toString(), uriAfter); + assert.equal( + resource.renameResourceUri.toString(), + uriAfter.toString() + ); }; test('Renamed (pre 2.19)', async () => { - await testRename('RENAMED a.txt', 'a.txt', 'a.txt'); + await testRename( + 'RENAMED a.txt', + 'a.txt', + 'a.txt', + ResourceStatus.RENAMED + ); }); test('Renamed (since 2.19)', async () => { - await testRename('RENAMED a.txt -> b.txt', 'a.txt', 'b.txt'); + await testRename( + 'RENAMED a.txt -> b.txt', + 'a.txt', + 'b.txt', + ResourceStatus.RENAMED + ); }); test('Renamed (since 2.23)', async () => { - await testRename('EDITED a.txt -> b.txt', 'a.txt', 'b.txt'); + await testRename( + 'EDITED a.txt -> b.txt', + 'a.txt', + 'b.txt', + ResourceStatus.MODIFIED + ); }); } @@ -279,6 +291,12 @@ export function TagSuite(this: Suite): void { } export function CleanSuite(this: Suite): void { + let rootUri: Uri; + + before(() => { + rootUri = workspace.workspaceFolders![0].uri; + }); + test('Clean', async () => { const swm: sinon.SinonStub = this.ctx.sandbox.stub( window, @@ -306,7 +324,12 @@ export function CleanSuite(this: Suite): void { .resolves(); await fakeFossilStatus(execStub, 'EXTRA a.txt\nEXTRA b.txt'); await repository.updateModelState(); - assert.equal(repository.untrackedGroup.resourceStates.length, 2); + assertGroups(repository, { + untracked: [ + [Uri.joinPath(rootUri, 'a.txt').fsPath, ResourceStatus.EXTRA], + [Uri.joinPath(rootUri, 'b.txt').fsPath, ResourceStatus.EXTRA], + ], + }); const swm: sinon.SinonStub = this.ctx.sandbox.stub( window, 'showWarningMessage' @@ -342,7 +365,13 @@ export function CleanSuite(this: Suite): void { 'EXTRA a.txt\nEXTRA b.txt\nEXTRA c.txt' ); await repository.updateModelState(); - assert.equal(repository.untrackedGroup.resourceStates.length, 3); + assertGroups(repository, { + untracked: [ + [Uri.joinPath(rootUri, 'a.txt').fsPath, ResourceStatus.EXTRA], + [Uri.joinPath(rootUri, 'b.txt').fsPath, ResourceStatus.EXTRA], + [Uri.joinPath(rootUri, 'c.txt').fsPath, ResourceStatus.EXTRA], + ], + }); const showWarningMessage: sinon.SinonStub = this.ctx.sandbox.stub( window, 'showWarningMessage' diff --git a/src/test/suite/commitSuite.ts b/src/test/suite/commitSuite.ts index 50d9187..d166067 100644 --- a/src/test/suite/commitSuite.ts +++ b/src/test/suite/commitSuite.ts @@ -4,6 +4,7 @@ import * as sinon from 'sinon'; import { ExecStub, add, + assertGroups, cleanupFossil, fakeExecutionResult, fakeFossilStatus, @@ -12,7 +13,9 @@ import { } from './common'; import * as assert from 'assert/strict'; import * as fs from 'fs/promises'; -import { Suite, beforeEach } from 'mocha'; +import { Suite, before, beforeEach } from 'mocha'; +import { Reason } from '../../fossilExecutable'; +import { ResourceStatus } from '../../openedRepository'; export const commitStagedTest = async ( sandbox: sinon.SinonSandbox, @@ -50,16 +53,26 @@ export const commitStagedTest = async ( ]); }; -const singleFileCommitSetup = async (sandbox: sinon.SinonSandbox) => { +const singleFileCommitSetup = async ( + sandbox: sinon.SinonSandbox, + rootUri: Uri +) => { const repository = getRepository(); const execStub = getExecStub(sandbox); const statusStub = fakeFossilStatus(execStub, 'ADDED minimal.txt\n'); - await repository.updateModelState(); + await repository.updateModelState('test' as Reason); sinon.assert.calledOnce(statusStub); - assert.equal(repository.workingGroup.resourceStates.length, 1); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'minimal.txt').fsPath, ResourceStatus.ADDED], + ], + }); await commands.executeCommand('fossil.stageAll'); - assert.equal(repository.workingGroup.resourceStates.length, 0); - assert.equal(repository.stagingGroup.resourceStates.length, 1); + assertGroups(repository, { + staging: [ + [Uri.joinPath(rootUri, 'minimal.txt').fsPath, ResourceStatus.ADDED], + ], + }); const commitStub = execStub .withArgs(sinon.match.array.startsWith(['commit'])) .resolves(fakeExecutionResult()); @@ -67,6 +80,12 @@ const singleFileCommitSetup = async (sandbox: sinon.SinonSandbox) => { }; export function CommitSuite(this: Suite): void { + let rootUri: Uri; + + before(() => { + rootUri = workspace.workspaceFolders![0].uri; + }); + const clearInputBox = () => { const repository = getRepository(); repository.sourceControl.inputBox.value = ''; @@ -79,7 +98,14 @@ export function CommitSuite(this: Suite): void { const statusStub = fakeFossilStatus(execStub, 'ADDED fake.txt\n'); await repository.updateModelState(); sinon.assert.calledOnce(statusStub); - assert.equal(repository.workingGroup.resourceStates.length, 1); + assertGroups(repository, { + working: [ + [ + Uri.joinPath(rootUri, 'fake.txt').fsPath, + ResourceStatus.ADDED, + ], + ], + }); const commitStub = execStub .withArgs(['commit', 'fake.txt', '-m', 'non empty message']) .resolves(fakeExecutionResult()); @@ -124,8 +150,14 @@ export function CommitSuite(this: Suite): void { const sib = this.ctx.sandbox .stub(window, 'showInputBox') .resolves('test message all'); - assert.equal(repository.workingGroup.resourceStates.length, 1); - assert.equal(repository.stagingGroup.resourceStates.length, 1); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'a').fsPath, ResourceStatus.ADDED], + ], + staging: [ + [Uri.joinPath(rootUri, 'b').fsPath, ResourceStatus.ADDED], + ], + }); await commands.executeCommand('fossil.commitAll'); sinon.assert.calledOnce(sib); sinon.assert.calledOnceWithExactly(commitStub, [ @@ -141,7 +173,7 @@ export function CommitSuite(this: Suite): void { const statusStub = fakeFossilStatus(execStub, '\n'); await repository.updateModelState(); sinon.assert.calledOnce(statusStub); - assert.equal(repository.workingGroup.resourceStates.length, 0); + assertGroups(repository, {}); const sim: sinon.SinonStub = this.ctx.sandbox .stub(window, 'showInformationMessage') @@ -156,10 +188,7 @@ export function CommitSuite(this: Suite): void { test('Commit empty message', async () => { const repository = getRepository(); - const uri = Uri.joinPath( - workspace.workspaceFolders![0].uri, - 'empty_commit.txt' - ); + const uri = Uri.joinPath(rootUri, 'empty_commit.txt'); await fs.writeFile(uri.fsPath, 'content'); const execStub = getExecStub(this.ctx.sandbox); @@ -167,7 +196,9 @@ export function CommitSuite(this: Suite): void { const resource = repository.untrackedGroup.getResource(uri); await commands.executeCommand('fossil.add', resource); - assert.equal(repository.stagingGroup.resourceStates.length, 1); + assertGroups(repository, { + staging: [[uri.fsPath, ResourceStatus.ADDED]], + }); const commitStub = execStub.withArgs([ 'commit', 'empty_commit.txt', @@ -191,7 +222,6 @@ export function CommitSuite(this: Suite): void { }).timeout(6000); test('Commit creating new branch', async () => { - const rootUri = workspace.workspaceFolders![0].uri; const branchPath = Uri.joinPath(rootUri, 'branch.txt'); await fs.writeFile(branchPath.fsPath, 'branch content\n'); @@ -383,7 +413,8 @@ export function CommitSuite(this: Suite): void { .withArgs('fossil') .returns(configStub as any); const { commitStub, repository } = await singleFileCommitSetup( - this.ctx.sandbox + this.ctx.sandbox, + rootUri ); repository.sourceControl.inputBox.value = 'custom username test'; await commands.executeCommand('fossil.commitWithInput'); diff --git a/src/test/suite/common.ts b/src/test/suite/common.ts index 1be1ad8..72f3afe 100644 --- a/src/test/suite/common.ts +++ b/src/test/suite/common.ts @@ -11,6 +11,7 @@ import { FossilStdOut, FossilExecutablePath, ExecResult, + Reason, } from '../../fossilExecutable'; import { Model } from '../../model'; import { Repository } from '../../repository'; @@ -290,7 +291,13 @@ export async function add( addRes.stdout.trimEnd(), new RegExp(`${action}\\s+${filename}`) ); - await openedRepository.exec(['commit', filename, '-m', commitMessage]); + const commitRes = await openedRepository.exec([ + 'commit', + filename, + '-m', + commitMessage, + ]); + assert.equal(commitRes.exitCode, 0, 'Commit failed'); return fileUri; } @@ -298,13 +305,26 @@ export async function cleanupFossil(repository: Repository): Promise { const openedRepository: OpenedRepository = (repository as any).repository; if ( repository.workingGroup.resourceStates.length || - repository.stagingGroup.resourceStates.length + repository.stagingGroup.resourceStates.length || + repository.untrackedGroup.resourceStates.length ) { - await openedRepository.exec(['clean']); - await openedRepository.exec(['revert']); - await repository.updateModelState(); - assert.equal(repository.workingGroup.resourceStates.length, 0); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + const revertRes = await openedRepository.exec(['revert']); + assert.equal(revertRes.exitCode, 0); + + const cleanRes1 = await openedRepository.exec( + ['clean', '--verbose'], + 'Test: cleanupFossil' as Reason + ); + assert.equal(cleanRes1.exitCode, 0); + + const updateRes = await repository.updateModelState( + 'Test: cleanupFossil' as Reason + ); + assert.equal(updateRes, undefined); + // if we fail on the next line, it could be that there's fake status + assertGroups(repository, {}, 'Cleanup failed inside `cleanupFossil`'); + } else { + assertGroups(repository, {}, 'Totally unexpected state'); } for (const group of vscode.window.tabGroups.all) { const allClosed = await vscode.window.tabGroups.close(group); @@ -314,14 +334,37 @@ export async function cleanupFossil(repository: Repository): Promise { export function assertGroups( repository: Repository, - working: Map, - staging: Map + groups: { + working?: Readonly<[string, ResourceStatus]>[]; + staging?: Readonly<[string, ResourceStatus]>[]; + untracked?: Readonly<[string, ResourceStatus]>[]; + conflict?: Readonly<[string, ResourceStatus]>[]; + }, + message?: string ): void { - const to_map = (grp: FossilResourceGroup) => { + const group_to_map = (grp: FossilResourceGroup) => { return new Map( grp.resourceStates.map(res => [res.resourceUri.fsPath, res.status]) ); }; - assert.deepStrictEqual(to_map(repository.workingGroup), working); - assert.deepStrictEqual(to_map(repository.stagingGroup), staging); + assert.deepStrictEqual( + group_to_map(repository.workingGroup), + new Map(groups.working), + message + ); + assert.deepStrictEqual( + group_to_map(repository.stagingGroup), + new Map(groups.staging), + message + ); + assert.deepStrictEqual( + group_to_map(repository.untrackedGroup), + new Map(groups.untracked), + message + ); + assert.deepStrictEqual( + group_to_map(repository.conflictGroup), + new Map(groups.conflict), + message + ); } diff --git a/src/test/suite/mergeSuite.ts b/src/test/suite/mergeSuite.ts index 1c39a00..acebd87 100644 --- a/src/test/suite/mergeSuite.ts +++ b/src/test/suite/mergeSuite.ts @@ -13,10 +13,15 @@ import { import * as assert from 'assert/strict'; import * as fs from 'fs/promises'; import { FossilBranch, OpenedRepository } from '../../openedRepository'; -import { Suite } from 'mocha'; +import { Suite, before } from 'mocha'; import { Reason } from '../../fossilExecutable'; export function MergeSuite(this: Suite): void { + before(function () { + const repository = getRepository(); + assertGroups(repository, {}); + }); + test('Merge error is shown', async () => { const mergeExec = getRawExecStub(this.ctx.sandbox) .withArgs(sinon.match.array.startsWith(['merge'])) @@ -33,6 +38,7 @@ export function MergeSuite(this: Suite): void { .onFirstCall() .callsFake(items => { assert.ok(items instanceof Array); + assert.equal(items.length, 2); assert.equal(items[0].label, '$(git-branch) trunk'); return Promise.resolve(items[0]); }); @@ -97,7 +103,7 @@ export function MergeSuite(this: Suite): void { await commands.executeCommand('fossil.refresh'); await repository.updateModelState(); - assertGroups(repository, new Map(), new Map()); + assertGroups(repository, {}); const sqp: sinon.SinonStub = this.ctx.sandbox.stub( window, @@ -114,7 +120,7 @@ export function MergeSuite(this: Suite): void { sinon.assert.calledOnce(sib); await repository.updateModelState('test' as Reason); - assertGroups(repository, new Map(), new Map()); + assertGroups(repository, {}); }).timeout(5000); test('Cancel merge when a merge is in progress', async () => { diff --git a/src/test/suite/renameSuite.ts b/src/test/suite/renameSuite.ts index 7547980..d3158a1 100644 --- a/src/test/suite/renameSuite.ts +++ b/src/test/suite/renameSuite.ts @@ -17,15 +17,16 @@ import { Suite, before } from 'mocha'; import { Reason } from '../../fossilExecutable'; export function RenameSuite(this: Suite): void { + let rootUri: Uri; + before(async () => { - const repository = getRepository(); - await cleanupFossil(repository); + await cleanupFossil(getRepository()); + rootUri = workspace.workspaceFolders![0].uri; }); test('Rename a file', async () => { const oldFilename = 'not_renamed.txt'; const newFilename = 'renamed.txt'; - const rootUri = workspace.workspaceFolders![0].uri; await add(oldFilename, 'foo content\n', `add: ${oldFilename}`, 'ADDED'); const sim: sinon.SinonStub = this.ctx.sandbox.stub( @@ -46,25 +47,27 @@ export function RenameSuite(this: Suite): void { await eventToPromise(repository.onDidRunOperation); await repository.updateModelState(); - assertGroups( - repository, - new Map([[newFilePath.fsPath, ResourceStatus.RENAMED]]), - new Map() - ); + assertGroups(repository, { + working: [[newFilePath.fsPath, ResourceStatus.RENAMED]], + }); + await cleanupFossil(repository); }).timeout(6000); test("Don't show again", async () => { + const repository = getRepository(); + assertGroups(repository, {}, "Previous test didn't cleanup or failed"); + const config = () => workspace.getConfiguration('fossil'); assert.equal(config().get('enableRenaming'), true, 'contract'); - const rootUri = workspace.workspaceFolders![0].uri; const execStub = getExecStub(this.ctx.sandbox); const oldFilename = 'do_not_show.txt'; - await fs.writeFile(Uri.joinPath(rootUri, oldFilename).fsPath, '123'); + const oldUri = Uri.joinPath(rootUri, oldFilename); + await fs.writeFile(oldUri.fsPath, '123'); const newFilename = 'test_failed.txt'; const edit = new vscode.WorkspaceEdit(); const newFilePath = Uri.joinPath(rootUri, newFilename); - edit.renameFile(Uri.joinPath(rootUri, oldFilename), newFilePath); + edit.renameFile(oldUri, newFilePath); const sim = ( this.ctx.sandbox.stub( @@ -104,12 +107,19 @@ export function RenameSuite(this: Suite): void { } assert.equal(config().get('enableRenaming'), false, 'no update'); await config().update('enableRenaming', true); + assertGroups(repository, { + working: [[oldUri.fsPath, ResourceStatus.MODIFIED]], + }); + execStub.restore(); + await cleanupFossil(repository); }).timeout(3000); test('Rename directory', async () => { + const repository = getRepository(); + assertGroups(repository, {}, "Previous test didn't cleanup or failed"); + const oldDirname = 'not_renamed'; const newDirname = 'renamed'; - const rootUri = workspace.workspaceFolders![0].uri; const oldDirUrl = Uri.joinPath(rootUri, oldDirname); const newDirUrl = Uri.joinPath(rootUri, newDirname); await fs.mkdir(oldDirUrl.fsPath); @@ -124,7 +134,6 @@ export function RenameSuite(this: Suite): void { await Promise.all( oldUris.map(uri => fs.writeFile(uri.fsPath, `foo ${uri}\n`)) ); - const repository = getRepository(); const openedRepository: OpenedRepository = (repository as any) .repository; await openedRepository.exec(['add', oldDirname]); @@ -150,21 +159,22 @@ export function RenameSuite(this: Suite): void { await answeredYes; await eventToPromise(repository.onDidRunOperation); - await repository.updateModelState(); - - const ref: [string, ResourceStatus][] = newUris.map((url: Uri) => [ - url.fsPath, - ResourceStatus.RENAMED, - ]); - assertGroups(repository, new Map(ref), new Map()); + await repository.updateModelState('Test' as Reason); + + assertGroups(repository, { + working: newUris.map((url: Uri) => [ + url.fsPath, + ResourceStatus.RENAMED, + ]), + }); + await cleanupFossil(repository); }).timeout(10000); test('Relocate', async () => { const repository = getRepository(); - await cleanupFossil(repository); + assertGroups(repository, {}, "Previous test didn't cleanup or failed"); const oldFilename = 'not_relocated.txt'; const newFilename = 'relocated.txt'; - const rootUri = workspace.workspaceFolders![0].uri; const newUri = Uri.joinPath(rootUri, newFilename); const oldUri = await add( oldFilename, @@ -173,12 +183,11 @@ export function RenameSuite(this: Suite): void { 'ADDED' ); await fs.rename(oldUri.fsPath, newUri.fsPath); - await repository.updateModelState(); - assertGroups( - repository, - new Map([[oldUri.fsPath, ResourceStatus.MISSING]]), - new Map() - ); + await repository.updateModelState('Test' as Reason); + assertGroups(repository, { + working: [[oldUri.fsPath, ResourceStatus.MISSING]], + untracked: [[newUri.fsPath, ResourceStatus.EXTRA]], + }); this.ctx.sandbox .stub(window, 'showQuickPick') .onFirstCall() @@ -195,11 +204,9 @@ export function RenameSuite(this: Suite): void { repository.workingGroup.resourceStates[0] ); sinon.assert.calledOnce(sod); - assertGroups( - repository, - new Map([[newUri.fsPath, ResourceStatus.RENAMED]]), - new Map() - ); + assertGroups(repository, { + working: [[newUri.fsPath, ResourceStatus.RENAMED]], + }); }).timeout(10000); test('Relocate nothing', async () => { diff --git a/src/test/suite/resourceActionsSuite.ts b/src/test/suite/resourceActionsSuite.ts index 95d575b..01c7579 100644 --- a/src/test/suite/resourceActionsSuite.ts +++ b/src/test/suite/resourceActionsSuite.ts @@ -21,7 +21,7 @@ import { import * as assert from 'assert/strict'; import * as fs from 'fs/promises'; import { existsSync } from 'fs'; -import { Suite } from 'mocha'; +import { Suite, before } from 'mocha'; import { ResourceStatus } from '../../openedRepository'; import { FossilResource } from '../../repository'; @@ -51,13 +51,18 @@ async function documentWasShown( } export function resourceActionsSuite(this: Suite): void { + let rootUri: Uri; + + before(() => { + rootUri = workspace.workspaceFolders![0].uri; + }); + test('fossil add nothing', async () => { await commands.executeCommand('fossil.add'); }); test('fossil add', async () => { - const root = workspace.workspaceFolders![0].uri; - const uri = Uri.joinPath(root, 'add.txt'); + const uri = Uri.joinPath(rootUri, 'add.txt'); await fs.writeFile(uri.fsPath, 'fossil_add'); const repository = getRepository(); @@ -78,8 +83,12 @@ export function resourceActionsSuite(this: Suite): void { const repository = getRepository(); await repository.updateModelState('test' as Reason); sinon.assert.calledOnce(statusStub); - assert.equal(repository.untrackedGroup.resourceStates.length, 2); - assertGroups(repository, new Map(), new Map()); + assertGroups(repository, { + untracked: [ + [Uri.joinPath(rootUri, 'a.txt').fsPath, ResourceStatus.EXTRA], + [Uri.joinPath(rootUri, 'b.txt').fsPath, ResourceStatus.EXTRA], + ], + }); execStub.restore(); execStub = getExecStub(this.ctx.sandbox); statusStub = fakeFossilStatus(execStub, 'ADDED a.txt\nADDED b.txt'); @@ -89,15 +98,12 @@ export function resourceActionsSuite(this: Suite): void { await commands.executeCommand('fossil.addAll'); sinon.assert.calledOnce(statusStub); sinon.assert.calledOnceWithExactly(addStub, ['add', 'a.txt', 'b.txt']); - const root = workspace.workspaceFolders![0].uri; - assertGroups( - repository, - new Map(), - new Map([ - [Uri.joinPath(root, 'a.txt').fsPath, ResourceStatus.ADDED], - [Uri.joinPath(root, 'b.txt').fsPath, ResourceStatus.ADDED], - ]) - ); + assertGroups(repository, { + staging: [ + [Uri.joinPath(rootUri, 'a.txt').fsPath, ResourceStatus.ADDED], + [Uri.joinPath(rootUri, 'b.txt').fsPath, ResourceStatus.ADDED], + ], + }); }); test('fossil add untracked does not add working group', async () => { @@ -107,12 +113,20 @@ export function resourceActionsSuite(this: Suite): void { const statusStub = fakeFossilStatus(execStub, 'ADDED a\nADDED b'); await repository.updateModelState('test' as Reason); sinon.assert.calledOnce(statusStub); - assert.equal(repository.workingGroup.resourceStates.length, 2); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'a').fsPath, ResourceStatus.ADDED], + [Uri.joinPath(rootUri, 'b').fsPath, ResourceStatus.ADDED], + ], + }); await commands.executeCommand('fossil.addAll'); sinon.assert.calledOnce(statusStub); - assert.equal(repository.workingGroup.resourceStates.length, 2); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'a').fsPath, ResourceStatus.ADDED], + [Uri.joinPath(rootUri, 'b').fsPath, ResourceStatus.ADDED], + ], + }); }); test('fossil forget', async () => { @@ -138,7 +152,18 @@ export function resourceActionsSuite(this: Suite): void { // better branch coverage await commands.executeCommand('fossil.forget'); - assert.equal(repository.untrackedGroup.resourceStates.length, 1); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'a.txt').fsPath, ResourceStatus.ADDED], + [ + Uri.joinPath(rootUri, 'b.txt').fsPath, + ResourceStatus.MODIFIED, + ], + ], + untracked: [ + [Uri.joinPath(rootUri, 'c.txt').fsPath, ResourceStatus.EXTRA], + ], + }); await commands.executeCommand( 'fossil.forget', ...repository.untrackedGroup.resourceStates @@ -146,7 +171,6 @@ export function resourceActionsSuite(this: Suite): void { }).timeout(5000); test('Ignore', async () => { - const rootUri = workspace.workspaceFolders![0].uri; const uriToIgnore = Uri.joinPath(rootUri, 'autogenerated'); const urlIgnoredGlob = Uri.joinPath( rootUri, @@ -207,7 +231,12 @@ export function resourceActionsSuite(this: Suite): void { const execStub = getExecStub(this.ctx.sandbox); fakeFossilStatus(execStub, `ADDED a\nADDED b\n`); await repository.updateModelState(); - assert.equal(repository.workingGroup.resourceStates.length, 2); + assertGroups(repository, { + working: [ + [Uri.joinPath(rootUri, 'a').fsPath, ResourceStatus.ADDED], + [Uri.joinPath(rootUri, 'b').fsPath, ResourceStatus.ADDED], + ], + }); const testTd: TextDocument = { isUntitled: false } as TextDocument; const otd = this.ctx.sandbox @@ -254,8 +283,7 @@ export function resourceActionsSuite(this: Suite): void { const createTestResource = async (status: string) => { const repository = getRepository(); - const root = workspace.workspaceFolders![0].uri; - const uri = Uri.joinPath(root, 'open_resource.txt'); + const uri = Uri.joinPath(rootUri, 'open_resource.txt'); const execStub = getExecStub(this.ctx.sandbox); const statusStub = fakeFossilStatus( execStub, @@ -345,8 +373,7 @@ export function resourceActionsSuite(this: Suite): void { }); test('Add current editor uri', async () => { - const root = workspace.workspaceFolders![0].uri; - const uri = Uri.joinPath(root, 'opened.txt'); + const uri = Uri.joinPath(rootUri, 'opened.txt'); await fs.writeFile(uri.fsPath, 'opened'); const repository = getRepository(); // make file available in 'untracked' group diff --git a/src/test/suite/setup.test.ts b/src/test/suite/setup.test.ts index ceb0da3..a2c2a52 100644 --- a/src/test/suite/setup.test.ts +++ b/src/test/suite/setup.test.ts @@ -468,11 +468,10 @@ function OpenSuite(this: Suite): void { }).timeout(3500); test('Close', async () => { - const repository = getRepository(); const executable = getExecutable(); const uri = workspace.workspaceFolders![0].uri; const cwd = uri.fsPath as FossilCWD; - await cleanupFossil(repository); + await cleanupFossil(getRepository()); const closeStub = this.ctx.sandbox .spy(executable, 'exec') .withArgs(cwd, sinon.match.array.startsWith(['close'])); diff --git a/src/test/suite/stateSuite.ts b/src/test/suite/stateSuite.ts index f2e4b11..0e8a443 100644 --- a/src/test/suite/stateSuite.ts +++ b/src/test/suite/stateSuite.ts @@ -1,6 +1,7 @@ import { Uri, window, workspace, commands } from 'vscode'; import * as sinon from 'sinon'; import { + assertGroups, cleanupFossil, fakeExecutionResult, fakeFossilStatus, @@ -9,8 +10,8 @@ import { } from './common'; import * as assert from 'assert/strict'; import * as fs from 'fs/promises'; -import { OpenedRepository } from '../../openedRepository'; -import { Suite, before } from 'mocha'; +import { OpenedRepository, ResourceStatus } from '../../openedRepository'; +import { Suite, after, before } from 'mocha'; import { Reason } from '../../fossilExecutable'; function PullAndPushSuite(this: Suite): void { @@ -208,8 +209,7 @@ export function UpdateSuite(this: Suite): void { }); test('Change branch to hash', async () => { - const repository = getRepository(); - await cleanupFossil(repository); + await cleanupFossil(getRepository()); const execStub = getExecStub(this.ctx.sandbox); const updateCall = execStub .withArgs(['update', '1234567890']) @@ -279,16 +279,15 @@ export function UpdateSuite(this: Suite): void { } export function StashSuite(this: Suite): void { + let uri: Uri; + test('Save', async () => { const repository = getRepository(); - const uri = Uri.joinPath( - workspace.workspaceFolders![0].uri, - 'stash.txt' - ); + uri = Uri.joinPath(workspace.workspaceFolders![0].uri, 'stash.txt'); await fs.writeFile(uri.fsPath, 'stash me'); - const siw = this.ctx.sandbox.stub(window, 'showInputBox'); - siw.onFirstCall().resolves('stashSave commit message'); + const sib = this.ctx.sandbox.stub(window, 'showInputBox'); + sib.onFirstCall().resolves('stashSave commit message'); const stashSave = getExecStub(this.ctx.sandbox).withArgs([ 'stash', @@ -303,6 +302,9 @@ export function StashSuite(this: Suite): void { await commands.executeCommand('fossil.add', resource); await commands.executeCommand('fossil.stashSave'); sinon.assert.calledOnce(stashSave); + assertGroups(repository, { + untracked: [[uri.fsPath, ResourceStatus.EXTRA]], + }); }).timeout(6000); test('Apply', async () => { @@ -311,6 +313,7 @@ export function StashSuite(this: Suite): void { const sqp = this.ctx.sandbox.stub(window, 'showQuickPick'); sqp.onFirstCall().callsFake(items => { assert.ok(items instanceof Array); + assert.equal(items.length, 1); assert.match( items[0].label, /\$\(circle-outline\) 1 • [a-f0-9]{12}/ @@ -319,6 +322,10 @@ export function StashSuite(this: Suite): void { }); await commands.executeCommand('fossil.stashApply'); sinon.assert.calledOnce(stashApply); + const repository = getRepository(); + assertGroups(repository, { + working: [[uri.fsPath, ResourceStatus.ADDED]], + }); }).timeout(6000); test('Drop', async () => { @@ -358,8 +365,9 @@ export function StashSuite(this: Suite): void { test('Snapshot', async () => { const repository = getRepository(); - assert.equal(repository.workingGroup.resourceStates.length, 1); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + assertGroups(repository, { + working: [[uri.fsPath, ResourceStatus.ADDED]], + }); const execStub = getExecStub(this.ctx.sandbox); const stashSnapshot = execStub.withArgs([ 'stash', @@ -385,6 +393,10 @@ export function StashSuite(this: Suite): void { sinon.assert.calledOnce(swm); sinon.assert.calledOnce(stashSnapshot); }).timeout(15000); + + after(async () => { + await cleanupFossil(getRepository()); + }); } export function PatchSuite(this: Suite): void { @@ -424,9 +436,16 @@ export function PatchSuite(this: Suite): void { } export function StageSuite(this: Suite): void { + let a_txt: string; + let b_txt: string; + let c_txt: string; + before(async () => { - const repository = getRepository(); - await cleanupFossil(repository); + await cleanupFossil(getRepository()); + const rootUri = workspace.workspaceFolders![0].uri; + a_txt = Uri.joinPath(rootUri, 'a.txt').fsPath; + b_txt = Uri.joinPath(rootUri, 'b.txt').fsPath; + c_txt = Uri.joinPath(rootUri, 'c.txt').fsPath; }); const statusSetup = async (status: string) => { @@ -440,15 +459,25 @@ export function StageSuite(this: Suite): void { await commands.executeCommand('fossil.unstageAll'); await statusSetup('ADDED a.txt\nEDITED b.txt\nEDITED c.txt'); const repository = getRepository(); - assert.equal(repository.workingGroup.resourceStates.length, 3); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + assertGroups(repository, { + working: [ + [a_txt, ResourceStatus.ADDED], + [b_txt, ResourceStatus.MODIFIED], + [c_txt, ResourceStatus.MODIFIED], + ], + }); await commands.executeCommand( 'fossil.stage', repository.workingGroup.resourceStates[0], repository.workingGroup.resourceStates[1] ); - assert.equal(repository.workingGroup.resourceStates.length, 1); - assert.equal(repository.stagingGroup.resourceStates.length, 2); + assertGroups(repository, { + working: [[c_txt, ResourceStatus.MODIFIED]], + staging: [ + [a_txt, ResourceStatus.ADDED], + [b_txt, ResourceStatus.MODIFIED], + ], + }); }); test('Stage (nothing)', async () => { @@ -459,32 +488,43 @@ export function StageSuite(this: Suite): void { await commands.executeCommand('fossil.unstage'); }); - test('Stage all', async () => { + test('Stage all / Unstage all', async () => { await commands.executeCommand('fossil.unstageAll'); await statusSetup('ADDED a.txt\nEDITED b.txt\nEDITED c.txt'); const repository = getRepository(); - assert.equal(repository.workingGroup.resourceStates.length, 3); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + const allStatuses = [ + [a_txt, ResourceStatus.ADDED] as const, + [b_txt, ResourceStatus.MODIFIED] as const, + [c_txt, ResourceStatus.MODIFIED] as const, + ]; + assertGroups(repository, { working: allStatuses }); await commands.executeCommand('fossil.stageAll'); - assert.equal(repository.workingGroup.resourceStates.length, 0); - assert.equal(repository.stagingGroup.resourceStates.length, 3); + assertGroups(repository, { staging: allStatuses }); + await commands.executeCommand('fossil.unstageAll'); + assertGroups(repository, { working: allStatuses }); }); test('Unstage', async () => { const repository = getRepository(); - await commands.executeCommand('fossil.unstageAll'); - assert.equal(repository.stagingGroup.resourceStates.length, 0); await statusSetup('ADDED a.txt\nEDITED b.txt\nEDITED c.txt'); - assert.equal(repository.workingGroup.resourceStates.length, 3); - assert.equal(repository.stagingGroup.resourceStates.length, 0); + const allStatuses = [ + [a_txt, ResourceStatus.ADDED] as const, + [b_txt, ResourceStatus.MODIFIED] as const, + [c_txt, ResourceStatus.MODIFIED] as const, + ]; + assertGroups(repository, { working: allStatuses }); await commands.executeCommand('fossil.stageAll'); - assert.equal(repository.workingGroup.resourceStates.length, 0); - assert.equal(repository.stagingGroup.resourceStates.length, 3); + assertGroups(repository, { staging: allStatuses }); await commands.executeCommand( 'fossil.unstage', repository.stagingGroup.resourceStates[1] ); - assert.equal(repository.workingGroup.resourceStates.length, 1); - assert.equal(repository.stagingGroup.resourceStates.length, 2); + assertGroups(repository, { + working: [[b_txt, ResourceStatus.MODIFIED]], + staging: [ + [a_txt, ResourceStatus.ADDED], + [c_txt, ResourceStatus.MODIFIED], + ], + }); }); } diff --git a/src/test/suite/timelineSuite.ts b/src/test/suite/timelineSuite.ts index 0360eea..30d07d2 100644 --- a/src/test/suite/timelineSuite.ts +++ b/src/test/suite/timelineSuite.ts @@ -23,8 +23,7 @@ export function timelineSuite(this: Suite): void { let file2uri: vscode.Uri; before(async () => { - const repository = getRepository(); - await cleanupFossil(repository); + await cleanupFossil(getRepository()); await add('file1.txt', 'line1\n', 'file1.txt: first'); await add('file1.txt', 'line1\nline2\n', 'file1.txt: second', 'SKIP'); await add( diff --git a/src/test/suite/utilitiesSuite.ts b/src/test/suite/utilitiesSuite.ts index dbf11b6..506561f 100644 --- a/src/test/suite/utilitiesSuite.ts +++ b/src/test/suite/utilitiesSuite.ts @@ -3,11 +3,17 @@ import { Uri } from 'vscode'; import * as sinon from 'sinon'; import * as assert from 'assert/strict'; import * as fs from 'fs'; -import { getExecStub, getRepository } from './common'; +import { + assertGroups, + getExecStub, + getExecutable, + getRepository, +} from './common'; import { Suite, afterEach, beforeEach } from 'mocha'; import { debounce, memoize, sequentialize, throttle } from '../../decorators'; import { delay } from '../../util'; import { Reason } from '../../fossilExecutable'; +import { ResourceStatus } from '../../openedRepository'; function undoSuite(this: Suite) { test('Undo and redo warning', async () => { @@ -42,7 +48,9 @@ function undoSuite(this: Suite) { const execStub = getExecStub(this.ctx.sandbox); await repository.updateModelState('Test' as Reason); - assert.equal(repository.untrackedGroup.resourceStates.length, 1); + assertGroups(repository, { + untracked: [[undoTxtPath, ResourceStatus.EXTRA]], + }); showWarningMessage.onFirstCall().resolves('&&Delete file'); @@ -53,7 +61,9 @@ function undoSuite(this: Suite) { ); sinon.assert.calledOnceWithExactly( showWarningMessage, - 'Are you sure you want to DELETE undo-fuarw.txt?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.', + 'Are you sure you want to DELETE undo-fuarw.txt?\n' + + 'This is IRREVERSIBLE!\n' + + 'This file will be FOREVER LOST if you proceed.', { modal: true }, '&&Delete file' ); @@ -68,9 +78,11 @@ function undoSuite(this: Suite) { showInformationMessage.onFirstCall().resolves('Undo'); await vscode.commands.executeCommand('fossil.undo'); assert.ok(fs.existsSync(undoTxtPath)); + const executable = getExecutable(); + const fossilPath = (executable as any).fossilPath as string; assert.equal( showInformationMessage.firstCall.args[0], - `Undo 'fossil clean ${undoTxtPath}'?` + `Undo '${fossilPath} clean ${undoTxtPath}'?` ); showInformationMessage.onSecondCall().resolves('Redo'); @@ -78,7 +90,7 @@ function undoSuite(this: Suite) { assert.ok(!fs.existsSync(undoTxtPath)); assert.equal( showInformationMessage.secondCall.args[0], - `Redo 'fossil clean ${undoTxtPath}'?` + `Redo '${fossilPath} clean ${undoTxtPath}'?` ); }).timeout(7000); }